• ARC112F Die Siedler


    ARC112F Die Siedler

    题目来源:Atcoder, ARC112F Die Siedler

    题目大意

    题目链接

    你有若干张编号为 $ 1 $ 到 $ n $ 的卡片,和 $ m $ 包可以无限开的卡包,你可以以任意顺序执行任意多次以下操作:

    • 将 $ 2i $ 张编号为 $ i $ 的卡片换成一张编号为 $ i mod n + 1$ 的卡片。
    • 选择一个卡包打开并获得其中的所有卡片。

    你希望你拥有的卡片张数尽可能的少。

    你需要求出经过任意多次操作后你拥有的卡片的最小张数。

    数据范围:(1leq nleq 16, 1leq mleq 50)

    本题题解

    定义与一些基本性质

    定义一个“局面” (A) 是一个长度为 (n) 的非负整数序列,(A_i) 表示编号为 (i) 的卡片有多少张。

    定义两个局面的加法,就是把同类型的卡片数量相加。即:((A + B)_i = A_i + B_i)

    对于整数 (x),我们定义它的生成局面 (G(x)) 为:编号为 (1) 的卡片数量为 (x)、其他卡片数量为 (0) 的局面。

    定义一次逆向操作,是把 (1) 张编号为 (i + 1) 的卡片,换成 (2i) 张编号为 (i) 的卡片((1leq i < n))。

    定义 (g(A)) 表示把局面 (A) 里所有东西逆向操作到位置 (1) 后,能得到多少张卡片。即:(g(A) = sum_{i = 1}^{n}A_i2^{i - 1}(i - 1)!)

    定义 (T(A)) 表示把局面 (A) 里所有东西逆向操作到位置 (1) 后,得到的局面。即,(T(A) = G(g(A)))

    定义 (f(A)) 表示对 (A) 里的数使用若干次正向操作后,能得到的最小卡片张数。(f(A)) 可以用贪心法求出,也就是只要任意位置能使用操作,我们就用一下,显然操作的顺序不影响结果。

    不难证明如下两条性质:

    • (f(A) = f(T(A)))
    • (f(A + B) = f(T(A) + T(B)))。(可加性)

    此外我们发现,任意一个局面 (A),都可以通过 (n) 次正向操作,使得 (A_1) 减小 (2^{n}n! - 1),其他位置数值不变。其实就是把 (2^{n}n!) 张编号为 (1) 的卡片,依次换为编号为 (2, 3, dots,n) 的卡片,再换回来,变成 (1) 张编号为 (1) 的卡片。称这样的 (n) 次正向操作,为一个基本变换


    暴力做法

    特判最开始所有卡片数量都为 (0) 的情况。以下认为初始时至少存在一种卡片数量 $ > 0$。

    把每个卡包也看做一个局面,记卡包 (i) 为局面 (C_i)。记初始局面为 (A)

    因为 (f(A) = f(T(A))),且 (f(A + B) = f(T(A) + T(B)))。所以只需要考虑 (T(C_i))(T(A)),对它们操作,能够得到的最优局面是什么。使用一次卡包 (i),就相当于令 (g(A)gets g(A) + g(C_i))。换句话说,我们把每个局面(和卡包)都映射为了一个数(也就是 (g(A)))。把局面之间的变换,转化为了数的变换

    (c_i = g(C_i))。设 (a = g(A))。那么最终能得到的,编号为 (1) 的卡片数量,可以写成如下形式:

    [v = a + x_1 c_1 + x_2 c_2 + dots + x_n c_n - y(2^{n}n! - 1) ]

    其中 (x_1dots x_n) 以及 (y) 都是非负整数(x_i) 表示开了多少次卡包 (i)(y) 表示使用了多少次基本变换。

    我们的目标,就是在所有可能的正整数 (v) 中,找出 (f(G(v))) 最小的那个。

    根据裴蜀定理,不定方程 (x_1 c_1 + x_2 c_2 + dots + x_n c_n - y(2^{n}n! - 1) = k) 有解,当且仅当 (k)(gcd(c_1, c_2, dots, c_n, (2^nn! - 1))) 的倍数。并且发现,只要满足该条件,就一定存在一组解使得 (x_1dots x_n)(y) 都非负。因为如果 (x_i < 0)(y < 0),可以令 (x_i exttt{+=}(2^nn! - 1), y exttt{+=} c_i),这样和是不变的,操作若干次后,总能让所有数非负。

    (d = gcd(c_1, c_2, dots, c_n, (2^nn! - 1)))。那么问题转化为:求一个正整数 (v),满足 (vequiv apmod{d}),且 (f(G(v))) 最小。

    (a' = amod d),那么我们可以从 (v = a', a' + d, a' + 2ddots) 一直枚举下去,看哪个 (v) 得到的局面最优。当然,枚举到 (2^nn! - 1) 后就不用再枚举了,因为循环了。具体来说,我们要求的是:

    [min_{substack{tgeq 0\a' + tdleq 2^nn!-1\a' + td eq 0}}{f(G(a' + td))} ]

    暴力枚举 (t),暴力计算 (f),则时间复杂度 (mathcal{O}(2^nn! cdot n))


    优化

    我们上述的枚举量,实际是 (mathcal{O}(frac{2^nn! - 1}{d})) 级别的,所以考虑对 (d) 进行根号分治

    (dgeq sqrt{2^nn! - 1}),直接按上述方法暴力。时间复杂度 (mathcal{O}(frac{2^nn! - 1}{d}cdot n)= mathcal{O}(sqrt{2^nn! - 1}cdot n))

    (d < sqrt{2^nn! - 1}),跑同余最短路。问题相当于求,(g(S)mod d = a') 的所有局面 (S) 里,(f(S)) 最小是多少。我们可以对所有 (0leq i < d), (1leq jleq n),从 (i)((i + 2^{j - 1}(j - 1)!)mod d) 连边,边权为 (1)。起点是所有 (2^{j - 1}(j - 1)!),初始权值为 (1)(起点不能设为 (0),因为 (v) 必须是正数)。求到点 (a') 的最短路即可。时间复杂度是 (mathcal{O}(dcdot n) = mathcal{O}(sqrt{2^nn! - 1}cdot n)) 的。

    注意到当 (nleq16) 时,(2^nn! - 1) 的因数中 (leq sqrt{2^nn!- 1}) 的最大因数为 (1214827)。因此上述做法可以通过本题。

    参考代码

    // problem: ARC112F
    #include <bits/stdc++.h>
    using namespace std;
    
    #define mk make_pair
    #define fi first
    #define se second
    #define SZ(x) ((int)(x).size())
    
    typedef unsigned int uint;
    typedef long long ll;
    typedef unsigned long long ull;
    typedef pair<int, int> pii;
    
    template<typename T> inline void ckmax(T& x, T y) { x = (y > x ? y : x); }
    template<typename T> inline void ckmin(T& x, T y) { x = (y < x ? y : x); }
    
    const int MAXN = 16, MAXM = 50;
    const int INF = 1e9;
    const ll LL_INF = 1e18;
    
    int n, m;
    ll coef[MAXN + 5];
    
    struct State {
    	int a[MAXN + 5];
    	ll val;
    	ll read() {
    		val = 0;
    		for (int i = 1; i <= n; ++i) {
    			cin >> a[i];
    			val += coef[i - 1] * a[i];
    		}
    		return val;
    	}
    	State() {}
    };
    
    State A, C[MAXM + 5];
    
    ll gcd(ll x, ll y) { return (!y) ? x : gcd(y, x % y); }
    
    int main() {
    	cin >> n >> m;
    	coef[0] = 1;
    	for (int i = 1; i <= n; ++i) {
    		coef[i] = coef[i - 1] * 2 * i;
    	}
    	
    	ll a = A.read();
    	
    	if (!a) {
    		cout << 0 << endl;
    		return 0;
    	}
    	
    	ll mod = coef[n] - 1;
    	ll d = mod;
    	
    	for (int i = 1; i <= m; ++i) {
    		d = gcd(C[i].read(), d);
    	}
    	
    	if (d < mod / d) {
    		
    		vector<int> dis(d);
    		queue<int> q;
    		
    		for (int i = 0; i < d; ++i) {
    			dis[i] = INF;
    		}
    		for (int i = 0; i < n; ++i) {
    			dis[coef[i] % d] = 1;
    			q.push(coef[i] % d);
    		}
    		
    		while (!q.empty()) {
    			int u = q.front();
    			q.pop();
    			
    			for (int i = 0; i < n; ++i) {
    				int v = (u + coef[i]) % d;
    				if (dis[v] > dis[u] + 1) {
    					dis[v] = dis[u] + 1;
    					q.push(v);
    				}
    			}
    		}
    		
    		cout << dis[a % d] << endl;
    	} else {
    		ll ans = LL_INF;
    		
    		for (ll v = a % d; v <= mod; v += d) {
    			if (v == 0) continue;
    			
    			ll f = 0;
    			ll vv = v;
    			for (int i = 1; i <= n; ++i) {
    				// f += vv % (2 * i);
    				// vv /= 2 * i;
    				f += vv % coef[i] / coef[i - 1];
    			}
    			ckmin(ans, f);
    		}
    		
    		cout << ans << endl;
    	}
    	
    	return 0;
    }
    
  • 相关阅读:
    获取当前时区时间
    python lambda表达式详解
    Odoo 12开发之开发环境准备
    初步了解odoo12
    web前端面试题
    实现一个优先级队列
    面试题
    python读取和生成excel文件
    Django基础
    virtualenv
  • 原文地址:https://www.cnblogs.com/dysyn1314/p/14423706.html
Copyright © 2020-2023  润新知