bzoj2064: 分裂(集合DP)

  ......咸鱼了将近一个月,因为沉迷ingress作业越来越多一直没时间搞OI呜呜呜

  题目大意:有一个初始集合(n个元素)和一个目标集合(m个元素)(1<=n,m<=10),两个操作

         操作①将集合里的两个数合成一个数

         操作②将集合的一个数分成两个数

       问对初始集合最少进行几次操作可以到达目标集合

  ...从来没做过集合DP题,看见这题一脸懵逼>_<

  看了题解之后目瞪口呆,思路好神,又学会了新技巧(可能是我以前比较傻才不会QAQ

  显然最多的次数就是将初始集合全部合成一个数然后再分成目标集合,也就是次数上界为n+m-2

  如果我们可以找到初始集合的某个子集的元素和目标集合的某个子集的元素和相等,那么我们可以少合一次,少分一次,也就是说我们每多找到一个元素和相等的子集我们的次数就可以-2。换句话说把初始集合和目标集合分成尽量多的子集让这些子集都能对应(子集元素和相等),如果能分成x个子集那么次数就是n+m-2x

  所以问题转化成怎么分最多子集能相对应。f[S1][S2]为把S1和S2最多能分成几个能相对应的子集(子集元素和相等)。

  当S1的元素和!=S2的元素和,就没法用上所有元素分成相对应的子集,那我们就枚举S1中的元素i或S2中的一个元素j,就有f[S1][S2]=max(f[S1^i][S2],f[S1][S2^j])

  当S1的元素和==S2的元素和,我们可以用上全部元素,在这时候我们要是去掉其中一个元素,肯定会少一个子集,于是我们还是枚举S1中的元素i或S2中的一个元素j,就有f[S1][S2]=max(f[S1^i][S2],f[S1][S2^j])+1

  新技巧:......原来枚举子集用二进制枚举就可以了,以前我怎么这么傻QAQ

代码如下:

#include<iostream>
#include<cstring>
#include<cstdio>
#include<cstdlib>
using namespace std;
int n,m,sum1[1024],sum2[1024],f[1024][1024];
int lowbit(int x){return x&-x;}
int main()
{
    scanf("%d",&n);
    for(int i=0;i<n;i++)scanf("%d",&sum1[1<<i]);
    scanf("%d",&m);
    for(int i=0;i<m;i++)scanf("%d",&sum2[1<<i]);
    for(int i=1;i<(1<<n);i++)sum1[i]=sum1[i^lowbit(i)]+sum1[lowbit(i)];
    for(int i=1;i<(1<<m);i++)sum2[i]=sum2[i^lowbit(i)]+sum2[lowbit(i)];
    for(int i=1;i<(1<<n);i++)
    for(int j=1;j<(1<<m);j++)
    {
        for(int k=0;k<max(n,m);k++)
        {
            if(i&(1<<k))f[i][j]=max(f[i^(1<<k)][j],f[i][j]);
            if(j&(1<<k))f[i][j]=max(f[i][j^(1<<k)],f[i][j]);            
        }
        if(sum1[i]==sum2[j])f[i][j]++;
    }
    printf("%d\n",n+m-2*f[(1<<n)-1][(1<<m)-1]);
}
View Code

 

转载于:https://www.cnblogs.com/Sakits/p/6771675.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值