题目链接

B-筱玛爱阅读(状压dp)_状压dp

如何输入价格顺序是不变就是比较简单的状压dp,可是它这个价格是可以改变顺序的。

可能稍微就有点难想

​参考题解​

cnt[i]记录i在二进制下有几个1,用dp预处理;

把打折方案按位压到二进制里,状压dp套路

枚举状态i,考虑i的子集j,如果j的补集x==i^j存在打折方案,

那么由于dp[j]已经选择了从大到小的第cnt[j]本书,x只能去选从大到小的第cnt[i]本书

也就是说,j选的是前cnt[j]本的最便宜的,i选的是前cnt[i]本书的最便宜的

那么先选j再选x,x实际对应[cnt[j]+1,cnt[i] ]本里最便宜那本

由于j为i的子集,有cnt[j]<=cnt[i]且等号只在j==i时成立,所以两次不会选同一本书


不过他代码里关于dp[i|(1<<j)]=max(dp[i|(1<<j)],dp[i]); 这个的解析是错的,我这里更正过来了,如果价格的顺序是不变的,

那么只需要这一行的转移方程就可以了。





#include<bits/stdc++.h>
using namespace std;
const int N=15;
const int M=1<<N;
int n,m,k,now,v,sum;
int a[N+5],cnt[M],dp[M];//cnt[i]记录i在二进制下有几个1
bool vis[M];
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i)
scanf("%d",&a[i]);
sort(a+1,a+n+1,greater<int>());

for(int i=1;i<=m;++i)
{
scanf("%d",&k);
now=0;
while(k--)
{
scanf("%d",&v);
now|=(1<<(v-1));
}
vis[now]=1;
}
for(int i=1;i<M;++i)
cnt[i]=cnt[i&(i-1)]+1;//从i-lowbit(i)转移
for(int i=0;i<M;++i)
{
if(vis[i])dp[i]=max(dp[i],a[cnt[i]]);

for(int j=i;j;j=(j-1)&i)//i的子集j
{
int x=i^j;//j关于i的补集x
if(!vis[x])continue;//用x来免第cnt[i]大的价格
//printf("x:%d i%d j:%d\n",x,i,j);
dp[i]=max(dp[i],dp[j]+a[cnt[i]]);
}

for(int j=0;j<n;++j)//往上更新 以i为子集的 状态x 也能采用i的打折方案,迭代更新
dp[i|(1<<j)]=max(dp[i|(1<<j)],dp[i]);


}
for(int i=1;i<=n;++i)
sum+=a[i];
printf("%d\n",sum-dp[(1<<n)-1]);
return 0;
}
/*
4 2
1 2 3 4
2 1 2
2 3 4
*/