CF1246E To Make 1 題解
阿新 • • 發佈:2021-11-02
( 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; }