状压DP 分裂

问题 E: 分裂
时间限制: 1 Sec 内存限制: 128 MB
提交: 53 解决: 24
[提交][状态][讨论版]
题目描述
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。就是把所有的聚成一堆,再拆成m堆。那么如果把较少的子集合并成一堆后与结束状态的某些子集合并后值一样,那么就没必要再把它合并成一大块了,这样步数就减少了。
很容易想到状压。f[i][j]表示对于初始状态的子集i和结束状态的子集j,二者最多可产生多少堆。分别枚举i,j的子集,先直接转移,找到一种堆数最大的方案,如果i的值==j的值,那就一定可以再合并出一堆。
最后步数=n+m-2*f[totn][totm]
可能是我傻。。。枚举子集时强行加了一个2^n。。。其实只要枚举去掉一个元素的子集就行了,因为这个子集已从其他子集转移过了。所以就没必要把所有子集都枚举一遍了。

#include <cstdio>
int read()
{
    int sum=0,f=1;char x=getchar();
    while(x<'0'||x>'9'){if(x=='-')f=-1;x=getchar();}
    while(x>='0'&&x<='9'){sum=(sum<<3)+(sum<<1)+x-'0';x=getchar();}
    return sum*f;
}
int n,m,xp[25],f[1030][1030],sn[1030],sm[1030];
int main()
{
    xp[0]=1;for(int i=1;i<=11;i++)xp[i]=xp[i-1]<<1;
    n=read();for(int i=1;i<=n;i++)sn[xp[i-1]]=read();
    m=read();for(int i=1;i<=m;i++)sm[xp[i-1]]=read();
    for(int i=1;i<xp[n];i++)sn[i]=sn[i&-i]+sn[i^(i&-i)];
    for(int i=1;i<xp[m];i++)sm[i]=sm[i&-i]+sm[i^(i&-i)];
    for(int i=1;i<xp[n];i++)
        for(int j=1;j<xp[m];j++)
        {
            for(int k=0;k<n;k++)
                if((xp[k]&i)&&f[i^xp[k]][j]>f[i][j])f[i][j]=f[i^xp[k]][j];
            for(int k=0;k<m;k++)
                if((xp[k]&j)&&f[i][j^xp[k]]>f[i][j])f[i][j]=f[i][j^xp[k]];
            if(sn[i]==sm[j])f[i][j]++;
        }
    printf("%d\n",n+m-2*f[xp[n]-1][xp[m]-1]);
}
posted @ 2017-10-20 18:38  Hzoi_QTY  阅读(116)  评论(0编辑  收藏  举报