首页 > 代码库 > 【BZOJ2064】分裂 状压DP
【BZOJ2064】分裂 状压DP
【BZOJ2064】分裂
Description
背景:和久必分,分久必和。。。题目描述:中国历史上上分分和和次数非常多。。通读中国历史的WJMZBMR表示毫无压力。同时经常搞OI的他把这个变成了一个数学模型。假设中国的国土总和是不变的。每个国家都可以用他的国土面积代替,又两种可能,一种是两个国家合并为1个,那么新国家的面积为两者之和。一种是一个国家分裂为2个,那么2个新国家的面积之和为原国家的面积。 WJMZBMR现在知道了很遥远的过去中国的状态,又知道了中国现在的状态,想知道至少要几次操作(分裂和合并各算一次操作),能让中国从当时状态到达现在的状态。
Input
第一行一个数n1,表示当时的块数,接下来n1个数分别表示各块的面积。第二行一个数n2,表示现在的块,接下来n2个数分别表示各块的面积。
Output
一行一个数表示最小次数。
Sample Input
1 6
3 1 2 3
Sample Output
2 数据范围: 对于100%的数据,n1,n2<=10,每个数<=50 对于30%的数据,n1,n2<=6,
题解:传说中的“传言可不,会意可只”的状压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
声明:以上内容来自用户投稿及互联网公开渠道收集整理发布,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任,若内容有误或涉及侵权可进行投诉: 投诉/举报 工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。