bzoj2064 分裂 (状压dp神题)

题意:

给n1个物品1,和n2个物品2,求最少的操作次数,使物品2变成物品1(可以拆分物品2,也可以聚拢)

分析:

数据范围小->状压或暴搜,考虑暴搜,不好存状态,因为把两个物品拼接起来物品的数量会改变。于是只能考虑状压。(考场上还有半个小时的时候极其兴奋地去打状压,哇塞我好聪明,哇塞我好强,竟然打出来了耶,最后发现,题看错了。。。看漏了可以拆分物品2来变成物品1)

这是我自认为自己绝顶聪明的代码(呕)

#include<bits/stdc++.h>
using namespace std;
#define N 15
int n1,n2,a[N],b[N],dp[N][(1<<N)],pos[N];
int get(int x)
{
    int ans=0;
    while(x){ if(x&1) ans++; x>>=1; }
    return ans;
}
int check(int x,int y,int val)
{
    int rx=x,ry=y,cnt=0,wei=0,tot=0;
    while(ry){
        wei++;
        if((!(ry&1))&&(rx&1)) return -1;
        if((!(rx&1))&&(ry&1)) pos[++cnt]=wei;
        rx>>=1,ry>>=1;
    }
    for(int i=1;i<=cnt;i++) tot+=b[pos[i]];
    if(tot!=val) return -1;
    return cnt;
}
int main()
{
    freopen("Miku.in","r",stdin);
    freopen("Miku.out","w",stdout);
    scanf("%d",&n1);
    for(int i=1;i<=n1;i++) scanf("%d",&a[i]);
    scanf("%d",&n2);
    for(int i=1;i<=n2;i++) scanf("%d",&b[i]);
    if(n1==1) { printf("%d\n",n2-1); return 0 ; }
    memset(dp,0x7f7f7f,sizeof(dp));
    for(int i=0;i<(1<<n2);i++){
        int xx=check(i,(1<<n2)-1,a[1]);
        if(xx!=-1)
        dp[1][i]=xx-1;
    }
     
    for(int i=1;i<=n1-1;i++)
     for(int j=0;j<(1<<n2);j++) 
      for(int k=0;k<(1<<n2);k++){
          int xx=check(j,k,a[i+1]);
          if(get(j)<get(k)&&xx!=-1)
          dp[i+1][j]=min(dp[i+1][j],dp[i][k]+xx-1);
      }
    printf("%d\n",dp[n1][0]);
}
/*
4 5 3 6 4
7 2 3 4 3 4 1 1


ans 3
*/
SB看错题写的代码

 

  所以说正解是怎么dp的呢?拆分的聚拢的物品数频频在变,无论怎样都不好转移,去找题的特征与性质。

  我们可以发现(不,我没有发现),最多的操作次数是n1+n2-2次就是把它所有聚拢又拆分后的次数。然后我们发现, 如果初始状态中挑出 k1块的面积刚好等于目标状态中k2块的面积, 我们可以单独让这几块通过 k1+k2-2操作达到目标状态, 剩余的n1-k1与n2-k2可以通过分裂合并达到目标状态, 总操作数会变为 n1+n2-4次,就减少了两次。(引用

  所以我们要做的是找到尽量多的这样的关系来减少使用次数。先把n2的物品设为负数,sum是辅助数组帮助记录有没有完成从k1拼成k2个,若sum==0,则说明又多了一对上述的关系,总操作次数可以-2。最终答案就是总操作次数-可以减少的操作次数

 

#include<bits/stdc++.h>
using namespace std;
int n1,n2,a[30],sum[(1<<20)+5],dp[(1<<20)+5];
int main()
{
    freopen("Miku.in", "r", stdin);
    freopen("Miku.out", "w", stdout); 
    scanf("%d",&n1);
    for(int i=1;i<=n1;i++) scanf("%d",&a[i]);
    scanf("%d",&n2);
    for(int i=1;i<=n2;i++) scanf("%d",&a[i+n1]),a[i+n1]=-a[i+n1];
    int n=n1+n2;
    for(int i=1;i<(1<<n);i++){//1!!
     for(int j=1;j<=n;j++){
         if(i&(1<<j))//j xuan
         dp[i]=max(dp[i],dp[i^(1<<j)]),sum[i]+=a[j];
     }
         if(sum[i]==0)
           dp[i]++;
    }
    printf("%d\n",n-2*dp[(1<<n)-1]); 
}
/*
1 6
3 1 2 3
*/
正解

 

转载于:https://www.cnblogs.com/mowanying/p/11260707.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值