ARC112F 做题记录
主要记录一下这种题的做题过程。
- \(\text{Step 1}\)
我们发现第 \(j\) 种牌每满 \(2j\) 张就会向下一位进一,考虑套路:我们用第 \(1\) 种牌来表示第 \(j\) 种牌,权值为 \(W_{j-1}=\prod\limits_{i=1} ^{j-1} 2i\)。
这样我们可以只用第 \(1\) 种牌的数量,即一个数来代替一组牌,不妨设初始数字为 \(x\)。注意可以用第 \(n\) 种牌换第 \(1\) 种,用一个数表述就是把这个数减去 \(W_n-1=\prod\limits_{i=1}^n 2i-1\),限制为不能减成 \(0\)。
容易发现一个数对应了唯一一组卡牌,每组卡牌都有唯一一个表示的数,这样我们就完成了类似于双射的转化。
考虑每次加入一个卡包,类似的,用一个数表示每种卡包,不妨设为 \(b_{1...m}\),那么我们可以每次给 \(x\) 加上 \(b\) 中的一个数。
- \(\text{Step 2}\)
当 \(x>W_n-1\) 时,我们还可以给 \(x\) 减去 \(W_n-1\)。
我们用 \(x_i\) 表示加入卡包 \(i\) 的数量,用 \(y\) 表示减去 \(W_n-1\) 的次数,有
根据裴蜀定理,有解当且仅当 \((x'-x)|\gcd(b_1,b_2,...,b_m,W_n-1)\)。
而且对于方程 \(\sum\limits_{i=1}^m x_ib_i-y(W_n-1)=\gcd(b_1,b_2,...,b_m,W_n-1)\),我们一定可以构造出满足 \(x_1,x_2,..,x_m,y\ge 0\) 的一组解。
设 \(d=\gcd(b_1,b_2,...,b_m,W_n-1)\),设 \(f(S)\) 表示数字 \(S\) 贪心摊开后的卡牌数总和。一种暴力的方法是直接从最小的可能的 \(x'\) 开始枚举,每次加上 \(d\),取 \(f(x')\) 的最小值。
- \(\text{Step 3}\)
但是当 \(d\) 很小的时候会爆炸,我们考虑另一种可能的暴力,然后根号分治平衡两种做法。
考虑数字 \(x\) 在模 \(d\) 意义下可以互相到达,我们只需要找出模 \(d\) 意义下等于 \(x\) 的任意一个数字 \(x'\) 的最小 \(f(x')\)。
直接从答案入手,我们考虑最终 \(f(x')\) 的局面,枚举每一种牌的数量,然后找出合法的最小答案。
考虑同于最短路,在模 \(d\) 意义下进行,每次增加一张牌,并算出对应的 \(x'\)。
两种做法中前者 \(O(\dfrac{2^nn!}d)\),后者 \(O(nd)\),平衡一下是 \(O(\sqrt{2^nn!})\)。(\(W_n=2^nn!\))
接下来是魔法:由于 \(d\) 是 \(2^nn!-1\) 的因数,打表可得 \(\sqrt{2^nn!}\) 范围内 \(d\) 最大 \(1214827\)。
总结:这种题应该多思考如何通过操作特点来转化,把复杂的东西转化成易于观察的东西。
点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define pir pair<ll,ll>
#define mkp make_pair
#define fi first
#define se second
#define mkp make_pair
#define pb push_back
using namespace std;
const ll maxn=110;
ll n,m,c[maxn],a[maxn][maxn],b[maxn],x,d,dis[maxn*20000],q[maxn*20000],l,r,w[maxn];
ll calc(ll k){
ll s=0;
for(ll i=1;i<=n;i++){
s+=k%(2*i); k/=2*i;
} return s;
}
ll gcd(ll a,ll b){
if(!b) return a;
return gcd(b,a%b);
}
int main(){
scanf("%lld%lld",&n,&m);
w[0]=1;
for(ll i=1;i<=n;i++) w[i]=w[i-1]*2*i;
ll t=w[n]-1;
for(ll i=1,v=1;i<=n;i++)
scanf("%lld",c+i), x+=w[i-1]*c[i], x=(x-1)%t+1;
for(ll i=1;i<=m;i++){
for(ll j=1,v=1;j<=n;j++)
scanf("%lld",a[i]+j), b[i]+=w[j-1]*a[i][j], b[i]%=t;
} d=t;
for(ll i=1;i<=m;i++)
d=gcd(d,b[i]);
if(d>t/d){ ll ans=6e18;
for(ll i=(x-1)%d+1;i<=t;i+=d)
ans=min(ans,calc(i));
printf("%lld",ans);
} else{
l=1, r=0;
memset(dis,-1,sizeof dis);
for(ll i=1;i<=n;i++) dis[w[i-1]%d]=1;
for(ll i=0;i<d;i++)
if(dis[i]!=-1) q[++r]=i;
while(l<=r){
ll u=q[l++];
for(ll i=1;i<=n;i++){
ll v=(u+w[i-1])%d;
if(dis[v]==-1){
dis[v]=dis[u]+1;
q[++r]=v;
}
}
}
printf("%lld",dis[x%d]);
}
return 0;
}