bzoj2064 分裂 (状压dp)
2064: 分裂
Description
背景: 和久必分,分久必和。。。 题目描述: 中国历史上上分分和和次数非常多。。通读中国历史的WJMZBMR表示毫无压力。 同时经常搞OI的他把这个变成了一个数学模型。 假设中国的国土总和是不变的。 每个国家都可以用他的国土面积代替, 又两种可能,一种是两个国家合并为1个,那么新国家的面积为两者之和。 一种是一个国家分裂为2个,那么2个新国家的面积之和为原国家的面积。 WJMZBMR现在知道了很遥远的过去中国的状态,又知道了中国现在的状态,想知道至少要几次操作(分裂和合并各算一次操作),能让中国从当时状态到达现在的状态。
Input
第一行一个数n1,表示当时的块数,接下来n1个数分别表示各块的面积。 第二行一个数n2,表示现在的块,接下来n2个数分别表示各块的面积。
Output
一行一个数表示最小次数。
这题好神啊;
很容易想到想记录未使用的和已经拼成的数字状态,然后枚举子集转移;
但显然是\(O(2^{3(n+m)})\)的,十分naive;
发现最优解一定是把初始状态分成几组,每一组中先把所有数拼成一个再拆成结束状态,且组数越多越优;
\(dp[A][B]\)表示从初始状态\(A\)和结束状态\(B\)中选出一部分匹配能构成的最大组数;
设\(sum[]\)为该状态的数字和;
\(sum[A]!=sum[B]\)时,无法匹配,\(dp[A][B]=max(dp[A\bigoplus2^k][B],dp[A][B\bigoplus2^k])\);
\(sum[A]==sum[B]\)时,说明能匹配出新的一组,\(dp[A][B]=max(dp[A\bigoplus2^k][B],dp[A][B\bigoplus2^k])+1\);
\(ans=n+m+2-dp[2^n-1][2^m-1]\);
复杂度\(O(2^{n+m}(n+m))\);
AC GET☆DAZE
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<string>
#include<vector>
#include<cmath>
#include<queue>
#include<map>
#include<set>
#define N 1039
#define mod 20070831
#define inf 0x3f3f3f3f
#define ll long long
using namespace std;
int n,m,num[2][39],sum[2][N],dp[N][N];
int main()
{
scanf("%d",&n);
for(int a=1;a<=n;a++) scanf("%d",&num[0][a]);
scanf("%d",&m);
for(int a=1;a<=m;a++) scanf("%d",&num[1][a]);
for(int a=1;a<(1<<n);a++)
{
for(int b=0;a>>b;b++)
{
sum[0][a]+=(a>>b&1)*num[0][b+1];
}
}
for(int a=1;a<(1<<m);a++)
{
for(int b=0;a>>b;b++)
{
sum[1][a]+=(a>>b&1)*num[1][b+1];
}
}
for(int a=0;a<(1<<n);a++)
{
for(int b=0;b<(1<<m);b++)
{
for(int c=0;c<n;c++)
{
if(a>>c&1)
{
dp[a][b]=max(dp[a][b],dp[a^(1<<c)][b]);
}
}
for(int c=0;c<m;c++)
{
if(b>>c&1)
{
dp[a][b]=max(dp[a][b],dp[a][b^(1<<c)]);
}
}
if(sum[0][a]==sum[1][b]) dp[a][b]+=2;
}
}
printf("%d",n+m+2-dp[(1<<n)-1][(1<<m)-1]);
return 0;
}