牛客练习赛 49 :B 筱玛爱阅读 (子集DP)

题目描述:
筱玛是一个热爱阅读的好筱玛,他最喜欢的事情就是去书店买书啦! 一天,他来到一家有nn本书的书店,筱玛十分快乐,决定把这家店里所有的书全部买下来。 正巧今天店里在搞促销活动,包含若干个促销方案。每个促销方案是由指定的若干本书构成的集合,如果购买了该方案中所有的书,那么其中最便宜的一本书将免费。但是,每本书只能用于一个促销方案。 作为店里的VIP,筱玛会得到nn个价格标签。筱玛可以给每本书挑选一个价格标签,使得每个价格标签和每本书一一对应。 筱玛想要知道,在合理利用所有促销方案的情况下,买下所有书最小要多少钱。

(正解是子集dp,我想的是状压+背包dp,能做但是复杂度过不了(m小一点可以做),子集dp只做过一题,练得少导致根本没想到,再加上题目也读错了…)

分析:背包dp当然也可以做,但这题有个特性就是每本书只能用在一个方案,而不能用在两个或多个方案。也就是说有很多集合相交的情况是不需要考虑的,而状压背包dp仍然会枚举到集合相交的情况,需要二进制运算去判断。

正解:如果你选择了一个方案,那你只能从剩下还没用到方案里的书去考虑选择其它的方案,当前集合和当前集和的子集的异或运算就是你可用的合法状态,这样省略了很多无效的枚举。并且能用的方案只能是当前还可以用的书的子集,只需要枚举当前状态的子集,而不用枚举所有方案再来判断方案是否可以用。

由于价格标签是你贴,考虑到总的合法的方案是没有交集的,因此哪个方案在前哪个方案在后不影响答案。贪心的想,可以从让免费的金额最大来考虑,对于一个有k本书的方案,你至少需要选当前还有的前k大的标签,使得你免费的那本书尽量贵,下一个方案,只能再往后选i个标签,因为前面的方案已经用了前面的标签。dp时维护一下最优解所占的标签数即可(书本数)

代码

#include<bits/stdc++.h>
using namespace std;
const int maxn = 17;
int dp[1 << 16],a[maxn];
int sz[1 << 16];
bool used[1 << 16];
int fit[1 << 16];
int n,m,k,vis[17];
int main() {
	scanf("%d%d",&n,&m);
	int sum = 0;
	for(int i = 1; i <= n; i++) {
		scanf("%d",&a[i]);
		sum += a[i];
	}
	sort(a + 1,a + n + 1,greater<int>());
	for(int i = 1; i <= m; i++) {
		int mi = -1;
		int tot,x,cur = 0;
		scanf("%d",&tot);
		memset(vis,0,sizeof vis);
		for(int j = 1; j <= tot; j++) {
			scanf("%d",&x);
			cur |= (1 << (x - 1));
		}
		used[cur] = true;
		fit[cur] = tot;
	}
	int u = 0;
	for(int i = 0; i < (1 << n); i++) {
		for(int j = i; j; j = (j - 1) & i) {
			if(used[j]) {
				if(dp[i ^ j] + a[sz[i ^ j] + fit[j]] > dp[i]) {
					dp[i] = dp[i ^ j] + a[sz[i ^ j] + fit[j]];
					sz[i] = sz[i ^ j] + fit[j];
				}
			}
		}
	}
	printf("%d\n",sum - dp[(1 << n) - 1]);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值