首页 > 代码库 > 【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