BZOJ2064分裂 状压DP
Posted CQzhangyu
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了BZOJ2064分裂 状压DP相关的知识,希望对你有一定的参考价值。
【BZOJ2064】分裂
Description
Input
Output
Sample Input
Sample Output
题解:传说中的“传言可不,会意可只”的状压DP?(%neither_nor姜犇)
首先考虑最坏的情况,我们先将所有国家合到一起,然后再把它们一个个拆开,需要次数为n+m-2。
如果我们想让答案更优,方法就是先将所有国家合成x个国家,然后再将x个国家拆开,需要次数为n+m-2x,显然我们的x越大越好。也就是说,我们将把初始状态和结束状态分成尽可能多的集合,使得每个初始状态的集合都对应一个与它面积和相等的、结束状态的集合。
先想一种naive的做法:设f[i][j]表示已选出初始状态中的国家的状态为i(看不懂就直接认为i是一个状态),已选出结束状态中的国家的状态为j,所能形成的最多集合数。然后我们可以采用枚举子集的方法,时间复杂度惊人~
然后怎么办呢?神犇hz_lrd告诉我们,可以先假设我们已经知道了中间的状态,设初始状态中的集合为a,b,c,d,结束状态中的集合为A,B,C,D,然后我们把这些集合铺在两个序列上。第一个序列中依次放入集合a,b,c,d中的所有国家,第二个序列中依次放入A,B,C,D中的所有国家,然后对这两个序列求前缀和,其中a、A处的前缀和一定是相同的,b、B,c、C处也都相等,这就将问题转化成了:将两个序列随机排序,使得它们尽可能多的位置前缀和相等。
所以我们回到那个naive的做法,发现枚举子集根本没有必要,我们只需要枚举每个国家,一个一个加到两个状态i或j中去,一旦两个状态中的面积和相等,需要的次数就-2。为了转移更快,我们预处理出所有状态的面积和,然后转移就是O(2^(n+m)*(n+m))的了。
#include <cstdio> #include <cstring> #include <iostream> using namespace std; int n,m; int sn[1<<10],sm[1<<10],f[1<<10][1<<10],vn[12],vm[12]; int main() { int i,j,k; scanf("%d",&n); for(i=1;i<=n;i++) scanf("%d",&vn[i]); scanf("%d",&m); for(i=1;i<=m;i++) scanf("%d",&vm[i]); for(i=0;i<(1<<n);i++) for(j=1;j<=n;j++) if(i&(1<<j-1)) sn[i]+=vn[j]; for(i=0;i<(1<<m);i++) for(j=1;j<=m;j++) if(i&(1<<j-1)) sm[i]+=vm[j]; for(i=0;i<(1<<n);i++) { for(j=0;j<(1<<m);j++) { for(k=1;k<=n;k++) if(i&(1<<k-1)) f[i][j]=max(f[i][j],f[i^(1<<k-1)][j]); for(k=1;k<=m;k++) if(j&(1<<k-1)) f[i][j]=max(f[i][j],f[i][j^(1<<k-1)]); f[i][j]+=(sn[i]==sm[j])<<1; } } printf("%d",n+m+2-f[(1<<n)-1][(1<<m)-1]); return 0; }
以上是关于BZOJ2064分裂 状压DP的主要内容,如果未能解决你的问题,请参考以下文章