拖了一年了才补。。。去年现场赛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*/