2018 ccpc秦皇岛 Riddle 状压dp

这篇博客介绍了2018年CCPC秦皇岛竞赛中的一道题目,该题涉及到状压动态规划(DP)的解法。题目要求处理一个数组,数组元素可以作为物品或袋子,需要计算在一定条件下的方案总数。博主分享了解题思路,重点讲解了如何使用二进制表示状态,并通过dp数组记录方案数,利用计数01背包的转移过程进行状态转移。解题关键在于理解如何从当前状态转移到新状态,并正确应用转移方程`dp[i+j]=dp[j]*cnt[i]`。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

拖了一年了才补。。。去年现场赛3个小时没做出来,今年看了半个小时就有思路了= =

题目大意

有数组 a n {a_n} an其中 a i {a_i} ai可以作为物品,也可以作为袋子
如果作为物品, a i {a_i} ai作为物品的重量,不一定要装在袋子里
如果作为袋子, a i {a_i} ai作为袋子的容量,必须要装满物品。
对每一组输入,输出总的方案数
n < 15 , a i < 2000 n<15,{a_i}<2000 n<15,ai<2000

解题思路

一个合法的最终状态可以将物品分为几类:
1.作为物品且没有放在袋子里的,即完全没有考虑的物品
2.作为物品和袋子组成合法袋子
3.作为袋子且装满物品

可以用一个二进制数表示当前状态,第i位下标为1表示考虑这个东西(可以作为物品也可以作为状态),为0表示不考虑这个东西
d p [ s t a ] dp[sta] dp[sta]表示当前状态为 s t a sta sta时的方案总数.
而一个合法方案的转移必定是对于当前 s t a sta sta状态没有考虑到的东西产生了一个新的合法的袋子,转移到了一个考虑了更多东西的状态。(新加一个合法的袋子)
c n t [ x ] cnt[x] cnt[x]表示考虑x状态的东西作为 一 个 一个 合法的袋子的方案数

整个 d p dp dp的转移过程可以看做是计数 01 01 01背包的转移过程,先枚举物品,再枚举背包转移。注意在枚举背包的时候一定是当前枚举的一个合法包的补集的子集,保证物品只会出现在这个合法包中。
设当前考虑的包状态为 i i i,当前的状态为 j j j转移方程如下:

dp[i+j]=dp[j]*cnt[i]

注意不能写 j > = 0 j>=0 j>=0 会死循环

#include <bits/stdc++.h>
using namespace std;
#define IOS ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define ll long long
#define int ll
#define debug cout<<"fuck"<<endl;
#define pb  push_back
#define endl '\n'
#define fi first
#define se second
#define db double
#define pii pair<int,int>
#define mp make_pair
const int mod=(int)1e9+7;
const int maxn=(1<<15);
int n,t;
int a[20];
int sum[maxn+5],dp[maxn+5];
int cnt[maxn+5];//第i位为1表示考虑这个数字,为0表示不考虑当前数字
signed main()
{
    //cout<<(1<<15)<<endl;
    IOS
    cin>>t;
    while(t--)
    {
        memset(sum,0,sizeof(sum));
        memset(cnt,0,sizeof(cnt));
        cin>>n;
        for(int i=0;i<n;i++)
        {
            cin>>a[i];
        }
        for(int i=0;i<(1<<n);i++)
        {
            dp[i]=1;
            for(int j=0;j<n;j++)
            {
                if(i&(1<<j))sum[i]+=a[j];//sum[i]表示当取状态i时的总和(假设状态i全部作为物品)
            }
        }
        //cnt[i]表示组成当前状态的一个背包
        for(int i=0;i<(1<<n);i++)//当前的状态
        {
            for(int j=0;j<n;j++)
            {
                if(i&(1<<j))
                {
                    if(sum[i]-a[j]==a[j])
                    {
                        cnt[i]++;
                    }
                }
            }
        }
        for(int i=0;i<(1<<n);i++)//当前的袋子
        {
            int x=(1<<n)-1-i;
            for(int j=x;;j=(j-1)&x)//枚举不在当前袋子里的状态
            {
                dp[i|j]+=dp[j]*cnt[i];
                if(j==0)break;//防止出现死循环
            }
        }
        cout<<dp[(1<<n)-1]<<endl;
    }

    return 0;
}



/*
3
3
1 1 1
5
1 1 2 2 3
10
1 2 3 4 5 6 7 8 9 10
*/
/*7 15 127*/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值