ARC112F Die Siedler

ARC112F Die Siedler

题目来源:Atcoder, ARC112F Die Siedler

题目大意

题目链接

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

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

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

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

数据范围:\(1\leq n\leq 16, 1\leq m\leq 50\)

本题题解

定义与一些基本性质

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

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

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

定义一次逆向操作,是把 \(1\) 张编号为 \(i + 1\) 的卡片,换成 \(2i\) 张编号为 \(i\) 的卡片(\(1\leq 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_1\dots 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_1\dots x_n\)\(y\) 都非负。因为如果 \(x_i < 0\)\(y < 0\),可以令 \(x_i\texttt{+=}(2^nn! - 1), y \texttt{+=} c_i\),这样和是不变的,操作若干次后,总能让所有数非负。

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

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

\[\min_{\substack{t\geq 0\\a' + td\leq 2^nn!-1\\a' + td \neq 0}}\{f(G(a' + td))\} \]

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


优化

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

\(d\geq \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)\bmod d = a'\) 的所有局面 \(S\) 里,\(f(S)\) 最小是多少。我们可以对所有 \(0\leq i < d\), \(1\leq j\leq n\),从 \(i\)\((i + 2^{j - 1}(j - 1)!)\bmod d\) 连边,边权为 \(1\)。起点是所有 \(2^{j - 1}(j - 1)!\),初始权值为 \(1\)(起点不能设为 \(0\),因为 \(v\) 必须是正数)。求到点 \(a'\) 的最短路即可。时间复杂度是 \(\mathcal{O}(d\cdot n) = \mathcal{O}(\sqrt{2^nn! - 1}\cdot n)\) 的。

注意到当 \(n\leq16\) 时,\(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;
}
posted @ 2021-02-20 22:42  duyiblue  阅读(317)  评论(0编辑  收藏  举报