【牛客】寒假训练营1 H-01背包,但是bit 题解

文章讲述了如何使用动态规划解决一个特殊的背包问题,其中物品的重量是通过按位或运算来决定是否可以选取,目标是找到在总重量不超过给定限制下,物品价值的最大和。

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

传送门:01背包,但是bit
标签:动态规划

题目大意

共有n件物品,每件物品有价值vi与重量ui两个属性。但特别地,所选物品的总重量并不是每件物品的重量和,而是所有所选物品的重量进行按位或运算的结果。请你计算,在所选物品总重量不超过m的情况下,所选物品的最大价值之和是多少(价值之和正常定义为所选物品价值的加和)。
输入:T组数据(1<=T<=1e4),每组数据第一行两个正整数n,m(1<=n<=1e5,1<=m<=1e8),分别代表物品总数和背包容量。接下来n行每行包括两个正整数vi和ui(1<=vi,ui<=1e8),分别代表第i个物品的价值和重量。
输出:每组数据一个正整数,代表最大价值之和。

算法分析

  • 首先我们看到数据范围m最大为1e8就知道肯定不能用普通的背包。先从简单的结论思考,假设所有物品的重量按位或运算的结果不超过m,那么我们一定全部都选;假设有某一个数大于m,那么无论它与谁进行或运算都不可能使结果小于等于m,所以一定不能选。这么一想我们就会发现,只要有一个筛选的标准,确定哪些物品能选哪些不能选,接下来只要贪心就行了。
  • 如果我们要保证一些数按位或的结果(这里假设为x)不超过m,就只有两种可能。第一种可能是:二进制下x为1的位在m中都为1,即x|m=m。第二种可能:存在某一位在x中为1,在m中为0,但是存在比它更高的某一位在x中为0,在m中为1。显然第一种情况出现的概率比较小,第二种情况才是普遍情况,所以我们从它入手。
  • 既然要确定某一位在x中为0,在m中为1,我们不妨直接枚举(设为第i位)。那么对于任意比i更高的位j,都要满足xj<=mj,只有这样才能保证最终或运算结果不大于m。对于满足条件的物品,我们自然是全部取。枚举m的每一位只需最多30次,而遍历所有物品只要n次。考虑到枚举时每次都往后推一位,我们可以一开始先定义一个变量x=2n-1,然后让其每次减去自己的lowbit,就能得到前i位为0,而后面所有位为1的数字。再让其与m和每个物品的u进行与运算就能更快判断出哪些物品是合法的。

代码实现

#include <iostream>
using namespace std;
long long v[100005],u[100005];
#define lowbit(x) (x)&(-x)
int main(){
	long long i,j,x,n,m,T,sum,mx;
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	cin>>T;
	while(T--){
		cin>>n>>m;
		mx=0LL;
		x=(1<<30)-1;
		for(i=1;i<=n;i++)
			cin>>v[i]>>u[i];
		for(i=0;i<=29;i++){
			x-=lowbit(x);
			if((m&(1<<i))==0)continue;
			sum=0LL;
			for(j=1;j<=n;j++)
				if(((u[j]&(1<<i))==0)&&(((m&x)|(u[j]&x))==(m&x)))
					sum+=v[j];
			mx=max(mx,sum);
		}
		sum=0LL;
		for(j=1;j<=n;j++)
			if((m|u[j])==m)
				sum+=v[j];
		mx=max(mx,sum);
		cout<<mx<<'\n';
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值