拼题A打卡奖励 (25 分),1355D,1362C;

7-2 拼题A打卡奖励 (25 分)(背包)

题意:

求,从 n 个物品中选(每个物品有体积 vi, 价值 wi ),总体积不超过 m 的最大价值?
n ≤ 1 e 3 , m ≤ 7 e 5 , v i ≤ 600 , w i ≤ 30 n ≤1e3, m ≤7e5, vi ≤600, wi ≤30 n1e3,m7e5,vi600,wi30

思路:

如果按照经典套路:求总体积不超过m的最大价值:
先枚举所有物品,再枚举所有体积,这样时间复杂度为O(n*m),超时。

所以,需要转换一下:因为最大总价值不超过 w i ∗ n = 3 e 4 wi*n=3e4 win=3e4,所以:
求总价值恰好为 m 时的,最小体积。
先枚举所有物品,再枚举所有价值,这样的复杂度为 n ∗ w i ∗ n = 3 e 7 n*wi*n=3e7 nwin=3e7,可以。
最后遍历所有价值,看价值最大能够到达多少,使得最小体积不超过 m。

状态表示 f [ j ] f[j] f[j],总价值恰好为 j 的最小体积。

初始化:因为求最小值,所以一开始初始化为正无穷。
但是要注意,f[0] 要赋值为 0:总价值为0,最小体积为0。

状态转移
前面枚举价值 j,从大到小循环,最小到 w[i]
f[j] = min(f[j], f[j-w[i]]+v[i])

Code:

const int N = 200010;
int T, n, m, a[N];
int v[N],w[N],f[N];

int main(){
	cin>>n>>m;
	
	int sum=0;
	for(int i=1;i<=n;i++) cin>>v[i];
	for(int i=1;i<=n;i++) cin>>w[i],sum+=w[i];
	
	mem(f,0x3f);
	f[0]=0;
	
	for(int i=1;i<=n;i++)
	{
		for(int j=sum;j>=w[i];j--)
		{
			f[j]=min(f[j],f[j-w[i]]+v[i]);
		}
	}
	while(f[sum]>m) sum--;
	cout<<sum;
	
	return 0;
}

这是求,总体积恰好为 j 时,总价值的最小值;


还可以求,总体积至少为 j 时,总价值的最小值:
和上面问题相同之处:状态表示
f [ j ] f[j] f[j],总体积至少为 j 时,总价值的最小值。

但是不同之处:状态转移

  • 当 j ≥ v[i] 时,说明总体积至少为 j,且大于当前物品体积,当前物品可选可不选。
    当前物品不选,状态更新:从前 i-1 个位置选,总体积至少为 j。
    当前物品选,状态更新:从前 i-1 个位置选,总体积至少为 j-v[i],再加上当前物品的价值w[i]。
  • 当 j < v[i] 时,说明总体积至少为 j,且小于当前物品体积。
    和原来的完全背包不同的是,这里的j是最小的总体积,可以小于当前体积,也就是说当前的物品也是可以拿的!
    当前物品不选,状态更新:从前 i-1 个位置选,总体积至少为 j。
    当前物品选,状态更新为当前物品的价值!
    这种状态下就不能从前 i-1 种物品中选,使得总体积至少为 j-v[i]。因为当前 j-v[i] 是小于0的。所以这个状态就直接更新成当前物品的价值。

Code:

	mem(f,0x3f,sizeof f);
	f[0]=0;
	
	for(int i=1;i<=n;i++){
		for(int j=m;j>=0;j--){
			if(j>=v[i]) f[j]=min(f[j],f[j-v[i]]+w[i]);
			else f[j]=min(f[j],w[i]);
		}
	}

例题:
P2918 [USACO08NOV]Buying Hay S
精卫填海


1355D. Game With Array(思维)

题意:

给出n,m。
要构造一个长度为n,总和为m的数列。
同时还要给出一个数k,满足没有连续的位置之和为 k 或者 m-k。
问,能否构造成功?

思路:

如果m ≥ 2*n的话,能够构造成功。
前n-1个位置设为2,最后一个位置补差。令k=1。
这样,k不能找到,m-k 也不能找到。

Code:

const int N = 200010, mod = 1e9+7;
int T, n, m, a[N];

int main(){
	cin>>n>>m;
	
	if(m>=n*2){
		cout<<"YES\n";
		for(int i=1;i<n;i++) cout<<2<<" ";
		cout<<m-(n-1)*2<<endl;
		cout<<1;
	}
	else{
		cout<<"NO\n";
	}
	
	return 0;
}

1362C. Johnny and Another Rating Drop(规律,进制)

题意:

定义两个数的贡献为其二进制中,对应位不同的位置个数。
给一个数n,判断1~n中,所有相邻数的贡献之和位多少?

思路:

0000
0001
0010
0011
0100
0101
0110
0111
1000
1001
1010
1011

发现规律:
第0位,贡献数为n;
第1位,贡献数为n/2;
第2位,贡献数为n/2/2;
第3位,贡献数为n/2/2/2... 

Code:

const int N = 200010;
ll T, n, m, a[N];

int main(){
	cin>>T;
	while(T--)
	{
		cin>>n;
		
		ll ans=0;
		while(n)
		{
			ans+=n;
			n/=2;
		}
		cout<<ans<<endl;
	}
	
	return 0;
}

挺难想到的。
对于这样的二进制找规律的题,应该想到从每一位下手,而不是看整个数。


  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值