[BZOJ] 2064: 分裂
注意到\(n\)很小,應該是狀壓DP
記原集合為\(S\),目標集合為\(T\),如果我們能把\(S\)分成\(x\)個不相交的非空子集,且這\(x\)個子集能和\(T\)中的一些不相交非空子集的和相等,那么最終答案就是\(n+m-2x\),其中\(n=|S|,m=|T|\)
因此我們要最大化\(x\),這就是DP的目標了
設\(f[x][y]\)表示\(S\)的子集\(x\)和\(T\)的子集\(y\),\(sum\)相等的最多能配幾對
若\(sum[x]\not=sum[y]\),枚舉刪去\(x\)或\(y\)的一個元素
\[ f[x][y]=max\{f[x\oplus i][y],f[x][y\oplus j]\} \]
若\(sum[x]=sum[y]\),情況看起來復雜了
為了轉移,我們似乎要再枚舉子集,復雜度不能接受
但是這樣想,既然\(sum[x]=sum[y]\),那么\(x\)和\(y\)至少配成了一對,若在其中刪去一個元素,一定會減少一對
因此
\[ f[x][y]=1+max\{f[x\oplus i][y],f[x][y\oplus j]\} \]
這樣省去了枚舉子集,復雜度有保障
最終復雜度\(O((n+m)2^{n+m})\)
騷操作:如何統計\(sum[x]\)?
以前一直寫
for(int S=0;S<(1<<n);S++)for(int i=0;i<n;i++)if(S&(1<<i))sum[S]+=a[i];這樣做是\(O(n2^n)\)的,實際上,完全可以省去一個\(O(n)\) (雖然\(n\)很小..)
類似統計二進制中1的個數一樣,可以復用\(S\)的子集,方法就是用lowbit
也就是\(sum[x]=sum[x\oplus lowbit(x)]+sum[lowbit(x)]\)
邊界是\(sum[1<<i]=a[i+1]\)
這樣是\(O(2^n)\)的
#include<algorithm> #include<iostream> #include<cstdio>using namespace std;inline int rd(){int ret=0,f=1;char c;while(c=getchar(),!isdigit(c))f=c=='-'?-1:1;while(isdigit(c))ret=ret*10+c-'0',c=getchar();return ret*f; } #define space putchar(' ') #define nextline putchar('\n') void _(int x){if(!x)return;_(x/10);putchar('0'+x%10);} void out(int x){if(!x)putchar('0');_(x);}const int MAXN = 10;inline void upmax(int &x,int y){x=max(x,y);}int a[MAXN],b[MAXN]; int f[1<<MAXN][1<<MAXN]; int sum1[1<<MAXN],sum2[1<<MAXN]; int n,m;int main(){n=rd();for(int i=1;i<=n;i++)sum1[1<<(i-1)]=a[i]=rd();m=rd();for(int i=1;i<=m;i++)sum2[1<<(i-1)]=b[i]=rd();for(int i=1;i<(1<<n);i++){sum1[i]=sum1[i^(i&-i)]+sum1[i&-i];}for(int i=1;i<(1<<m);i++){sum2[i]=sum2[i^(i&-i)]+sum2[i&-i];}for(int s=1;s<(1<<n);s++){for(int t=1;t<(1<<m);t++){for(int i=0;i<n;i++){if(s&(1<<i)) upmax(f[s][t],f[s^(1<<i)][t]);}for(int i=0;i<m;i++){if(t&(1<<i)) upmax(f[s][t],f[s][t^(1<<i)]);}if(sum1[s]==sum2[t])f[s][t]++;}}cout<<n+m-2*f[(1<<n)-1][(1<<m)-1];return 0; } 轉載于:https://www.cnblogs.com/ghostcai/p/9802403.html
總結
以上是生活随笔為你收集整理的[BZOJ] 2064: 分裂的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: OpenStack部署之小结
- 下一篇: 搞不清边缘计算几款产品差异?动动小手点这