【bzoj2064】【分裂】状态压缩表示合并子集

来源:互联网 发布:宝信软件同花顺 编辑:程序博客网 时间:2024/05/15 23:52

这里写图片描述
(上不了p站我要死了,画师当然是wlop大大啦)

感觉这个做法还是挺难想的。
但还是总结一下思路吧。。

“只可意会不可言传的状压dp”(乱说)

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,

当然,我们很自然的想到一种方法,就是先把所有的国家合并起来,再挨个拆开,这样的次数是n+m-2,也就是上限。

那么什么时候可以使操作次数减少呢?我们发现,当有一些子集的和相等时,直接将这些合并再拆开,就可以免去多余的合并操作。(然而我这一步愣是没想出来)

从反方面想,每当找到这样的子集,就相当于找到了一次没用的操作,就可以拿上限去减。但是,如果合并方式不同,可能会产生不同的“没用操作”,而我们希望找到一种“没用操作”最多的操作方案,此时就是dp了。
而每一个国家可以被选或不被选,在有两种状态的情况下,我们很容易想到状压,而数据范围也是可以的。

那么对于一个子集状态的01串f[s](这里我将原来以及现在的国家状态合并到了一个二进制数中),这样的集合一定是由其一个子集和一个元素(国家)合并过来,不同的合并方式会造成不同的“没用操作”。所以对于每个集合,枚举集合中的每个元素,就相当于枚举不同的合并操作,取max即可。
如果该状态s中原来国家和现在国家的面积之和相等,则该状态也是一个“没用操作”,答案要++

完整代码:

#include<cstdio>#include<cstring>#include<algorithm>using namespace std;int f[1<<20],sum1[1<<10],sum2[1<<10];int n,m,w[15],v[15];int main(){    scanf("%d",&n);    for(int i=1;i<=n;i++) scanf("%d",&w[i]);    scanf("%d",&m);    for(int i=1;i<=m;i++) scanf("%d",&v[i]);    for(int i=1;i<(1<<n);i++){        for(int j=i,k=1;j;j>>=1,k++) if(j&1) sum1[i]+=w[k];    }    for(int i=1;i<(1<<m);i++){        for(int j=i,k=1;j;j>>=1,k++) if(j&1) sum2[i]-=v[k];    }    for(int i=1;i<(1<<(n+m));i++){        for(int j=i,k=0;j;j>>=1,k++) if(j&1) f[i]=max(f[i],f[i^(1<<k)]);        if(sum1[i&((1<<n)-1)]+sum2[i>>n]==0) f[i]++;    }    printf("%d\n",n+m-2*f[(1<<(n+m))-1]);    return 0;}