[??记录]ARC112F Die Siedler

command_block

2021-10-27 16:32:56

Personal

**题意** : Snuke 使用 $n$ 种卡玩游戏,卡的编号为 $1\sim n$ 。初始时,Snuke 有 $c_i$ 张 $i$ 号卡。 游戏里有 $m$ 种卡组,第 $i$ 种卡组含 $j$ 号卡的数目为 $s_{i,j}$。 可以进行下列操作任意次: - 获取一整套卡组。 - 将 $2j$ 张 $j$ 号牌换成一张 $(j\bmod n)+1$ 号牌。 求 Snuke 手上牌数的最小值。 $n\leq 16,m\leq 50$ ,时限$\texttt{6s}$。 ------------ 神必题。 特判初始没有牌的情况 由于换卡总是有收益,不难证明能换就换是最优的。先用这个规则化简 $c$ 使得 $c_j<2j$ 。 我们把所有卡都倒着换成 $1$ 号卡,个数为 $C=\sum\limits_{i=1}^n2^{i-1}(i-1)!c_i$ 。根据能换就换的原则,这和原问题等价。 然后我们只需考虑仅有 $c_1$ 非零的情况,局面的变换转化为了数的变换。 ------------ 记 $f(X)$ 表示有 $X$ 个 $1$ 号牌,通过适当的交换可以得到的最小牌数,容易贪心 $O(n)$ 求出。 把卡包 $i$ 也转成一个数 $S_i$,使用一次相当于让 $C$ 加上 $S_i$。 注意到我们将 $1$ 换到 $2,3,...,n$ 再换回 $1$ ,可以减少 $2^nn!-1$ 张牌。 操作后 $C$ 的可能取值有 : $$C'=C+x_1S_1+x_2S_2+...+x_mS_m-y(2^nn!-1)$$ $C'$ 能取到当且仅当 $x_{1\sim m},y$ 有解。根据斐蜀定理,充要条件是 $\gcd(S_1,S_2,...S_m,2^nn!-1)|\big(C'-C\big)$ 记 $d=\gcd(S_1,S_2,...S_m,2^nn!-1)$ ,解集为 $C'=kd+(C\bmod d)$ ,$C'\leq 2^nn!-1$ 。 - 当 $d$ 较大时 直接暴力枚举 $C'$ 并计算 $f(C')$ ,复杂度为 $O(n\times 2^nn!/d)$ - 当 $d$ 较小时 考虑同余最短路。需要求的是满足 $C'=C\pmod d$ 的 $f(C')$ 的最小值。 记 $dis_u$ 为满足 $u=C\pmod d$ 的 $f(u)$ 的最小值。 对于所有 $i\in [0,d),j\in[1,n]$ 从 $i$ 向 $i+2^{j-1}(j-1)!$ 连边,边权为 $1$ ,表示增加一张 $j$ 号牌。 起点是所有 $2^{j-1}(j-1)!$ ,$dis$ 为 $1$ 。(注意不能将起点设为 $0$ 否则 $C=0\pmod d$ 时会出错) 用 BFS ,复杂度为 $O(nd)$ 。 ------------ 哪个算法快用那个,复杂度和 $2^nn!-1$ 的 $\leq\sqrt{2^nn!-1}$ 的最大因子有关。 接下来就是魔法了![打个表](https://www.wolframalpha.com/input/?i=Table%5B+Factor%5B+n%212%5En-1+%5D%2C+%7Bn%2C2%2C16%7D+%5D)可以发现 $2^nn!-1$ 的 $\leq\sqrt{2^nn!-1}$ 的因数最大值仅有 $1214827$ ,于是本题就做完了。 ```cpp #include<algorithm> #include<cstdio> #include<queue> #include<assert.h> #define ll long long using namespace std; ll gcd(ll a,ll b) {return !b ? a :gcd(b,a%b);} ll w[20];int n; ll calcC(int *c){ ll ret=0; for (int i=1;i<=n;i++)ret+=c[i]*w[i-1]; return ret; } int c[20]; int calcF(ll S) { int ans=0; for (int i=1;i<=n;i++){ans+=S%(2*i);S/=2*i;} return ans; } ll C,d;int m,dis[1220000]; queue<int> q; int main() { scanf("%d%d",&n,&m); w[0]=1;for (int i=1;i<=n;i++)w[i]=w[i-1]*i*2; for (int i=1;i<=n;i++)scanf("%d",&c[i]);C=calcC(c); d=w[n]-1; for (int j=1;j<=m;j++){ for (int i=1;i<=n;i++)scanf("%d",&c[i]); d=gcd(d,calcC(c)); } int ans=1000000000; if (w[n]/d<d){ ll a=C%d;if (!a)a=d; for (ll X=a;X<w[n];X+=d) ans=min(ans,calcF(X)); }else { for (int i=0;i<n;i++){ int u=w[i]%d; if (!dis[u]){dis[u]=1;q.push(u);} } while(!q.empty()){ int u=q.front();q.pop(); for (int i=0,v;i<n;i++){ v=(u+w[i])%d; if (!dis[v]){dis[v]=dis[u]+1;q.push(v);} } }ans=dis[C%d]; } printf("%d",ans); return 0; } ```