( 3100* ,状压 ,buff 叠满了你都不会)
题意
\(n\) 个正整数 \(a_i\) ,每次选两个数合并成另一个,将 \(x,y\) 合并为 \(x+y\) 不断除以固定的 \(k\) 直到除不尽,问能否最后只剩一个 \(1\) ,构造方案。
\((1\le n\le16,1\le k,\sum a_i\le2000,k\not|a_i)\)
题解
猜结论。
显然是将所有数都除以若干次 \(k\) 。
因此可以表示成 \(\sum_{i=1}^n\frac{a_i}{k^{b_i}}=1\) 。
必要性显然,现在证明这东西也充分:
令 \(B=\max b_i\) ,改写成 \(\sum_{i=1}^nk^{B-b_i}=k^B\) 。
尝试构造一组解:把所有 \(b_i=B\) 的拿出来选其中两个(由于没 \(k|a_i\) 所以一定可以)。
把这两个合并,由于最上面那个形式,所以 \(k|\sum_{b_i=B}a_i\) 。如果这一组和不被 \(k\) 整除,那合并后的形式一样。否则把这东西不断除以 \(k\) 丢后面,剩下 \(b_i=B\) 的要么没了,要不然由于这样能一直保证没有数被 \(k\) 整除仍然有两个。
知道这个结论了之后照着 DP 就能判断有没有解,用 bitset 优化一下。
构造解的时候根据 DP 数组,先把所有 \(b_i\) 求出来,然后递归按上面的构造方式求解(写个循环就行了)。
PS :这题直接随机选两个数合并 check 就能过……
代码:
#include <bits/stdc++.h>
using namespace std;
const int N=16,S=1<<16,K=2005;
int n,m,k,a[N];
bitset<K> f[S];
vector<int> p[N*10];
signed main(){
scanf("%d%d",&n,&k);
for(int i=0;i<n;i++) scanf("%d",&a[i]),m+=a[i];
f[0][0]=1;
for(int i=0;i<(1<<n);i++){
for(int j=m/k*k;j;j-=k) if(f[i][j]) f[i][j/k]=1;
for(int j=0;j<n;j++) if(i>>j&1^1) f[i|(1<<j)]|=f[i]<<a[j];
}
if(!f[(1<<n)-1][1]) return puts("NO"),0;
for(int st=(1<<n)-1,s=1,d=0;st;)
if(s*k<=m&&f[st][s*k]) s*=k,d++;
else for(int i=0;i<n;i++) if((st>>i&1)&&a[i]<=s&&f[st^(1<<i)][s-a[i]])
p[d].push_back(a[i]),s-=a[i],st^=(1<<i);
puts("YES");
for(int i=N*10-1;i;i--) while(!p[i].empty()){
int s=p[i].back();p[i].pop_back();
while(s%k) printf("%d %d\n",s,p[i].back()),s+=p[i].back(),p[i].pop_back();
int d=i;
while(s%k==0) s/=k,d--;
p[d].push_back(s);
}
return 0;
}