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) 的卡片数量,可以写成如下形式:
其中 (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) 后就不用再枚举了,因为循环了。具体来说,我们要求的是:
暴力枚举 (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;
}