Descripton
传送门
- 有n个数不被k整除的数a[i]。
- 每一次选择两个数x,y,删去这两个数并加入f(x+y):
f(x)=(xmodk=0)?f(x/k):x
- 求最后能不能得到1。如果能,输出每一次将哪两个数删去。
- n<=16,∑a[i],k<=2000
Solution
- 一道让我无从下手的题目。
- 由于会除以k,所以最终的数可以表示成∑ai∗k−bi=1。
- 如果我们知道了b的话,就可以推出操作的方案了。
- 具体来说,假设B=max(bi),一定有两个bi同时等于B。否则上式两边同时乘kB就变成
a+∑ai∗kB−bi=kB
- a不是k的倍数,左边不是,右边是,不成立。
- 那么这样就可以将这两个数和在一起了。并加入新的a和b。这个等式还是成立的。直到最后一个。
- 现在需要求出b。
- 状压DP,设f[S][x]=0/1,表示已经选了S集合里面的ai,∑ai∗k−bi=x是否成立。
- 每一次枚举一个没有选过的a,转移到f[S+(1<<i)][x+ai]。
- 并且可以转移到f[S][x/k].
- 这个转移相当于是按照bi从小到大的顺序加入a。
- 最后判定f[(1<<n)-1][1]。倒退回去即可得到b。
- 但是这样转移是O(2nn∑a[i])的。考虑把x那一维压进bitset里面。那前一个转移到x+ai的转移就可以通过左移ai位再或过去完成(这个我想了好久,之前一直以为是将S压起来)。时间除以32就可以过了。
- 总之,这题需要先化简每一个a对答案的贡献,再DP计算出b,用bitset优化。再通过b还原,其中还需要发现合并的性质。
- 一道神奇的题目。
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<bitset>
#define maxn 16
#define maxm 2005
using namespace std;
int n,k,a[maxn],sum,i,j,S,x,cnt,b[maxn];
bitset<maxm> f[1<<maxn];
void dg(int tot){
if (tot==n) return;
int B=0;
for(int i=0;i<=n-tot;i++)
B=max(B,b[i]);
int t1=0,t2=0;
for(int i=0;i<=n-tot;i++) if (b[i]==B){
if (!t1) t1=i; else
{t2=i;break;}
}
printf("%d %d\n",a[t1],a[t2]);
int cnt=0,tmp;
for(tmp=a[t1]+a[t2];tmp%k==0;tmp/=k) cnt++;
b[t1]=B-cnt,a[t1]=tmp;
for(int i=t2;i<n-tot;i++) a[i]=a[i+1],b[i]=b[i+1];
dg(tot+1);
}
int main(){
scanf("%d%d",&n,&k);
for(i=0;i<n;i++) scanf("%d",&a[i]),sum+=a[i];
f[0][0]=1;
for(S=0;S<1<<n;S++) if (f[S].count()>0){
for(i=sum;i>=1;i--) if (i%k==0)
f[S][i/k]=f[S][i/k]|f[S][i];
for(i=0;i<n;i++) if (!((S>>i)&1))
f[S|(1<<i)]=f[S|(1<<i)]|(f[S]<<a[i]);
}
if (f[(1<<n)-1][1]){
printf("YES\n");
S=(1<<n)-1,x=1,cnt=0;
for(i=1;i<=n;i++){
while (x*k<=sum&&f[S][x*k]) x*=k,cnt++;
for(j=0;j<n;j++) if (x>=a[j]&&(S&(1<<j))&&(f[S^(1<<j)][x-a[j]])){
b[j]=cnt,S^=1<<j,x-=a[j];
break;
}
}
dg(1);
} else printf("NO\n");
}