1. 程式人生 > 其它 >CF1246E To Make 1 題解

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;
}