CF1246E To Make 1 题解

( 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;
}
上一篇:【ybtoj高效进阶 21269】乐园之旅(期望DP)


下一篇:P1424 小鱼的航程(改进版) C语言 精简版及没事找事版