智乃想考一道完全背包(Easy version) [背包+合成虚拟物品]

传送门:CF

[前题提要]:感觉trick应该挺典的,但是这种trick题就是这样的,当你想到的时候就秒,不知道就想不出来.虽然感觉很典,但是和平时CF上的trick不太一样,故记录一下.


当我第一眼看到这道题的时候,其实我马上就想到了一个dp方程,可以使用 d p [ i ] [ j ] [ k ] dp[i][j][k] dp[i][j][k]来表示前 i i i个物品在容量为 j j j的背包下,第 i i i个物品选择 k k k个的方案数.这个dp方程是不难转移的,使用一下前缀最大值和后缀最大值优化一下就行.但是复杂度是 n ∗ m ∗ m n*m*m nmm的,显然过不去,但是你会发现你选择的物品最多只能是k左右两边500个,所以此时复杂度可以优化为 1000 ∗ m 2 1000*m^2 1000m2,仍然是 2 e 8 2e8 2e8,加上一些常数,不幸的是还是过不去.其实我个人感觉是可以放这个方法过的,时限开个4s差不多就行了,但是出题人可能就是为了让你用他的trick,故意卡的.那没办法,就当被教育了.

ok,现在开始讲一下这道题的trick,对于本题的限制,我们会发现假设我们想选一个 i i i此时的 i i i小于 k k k,那么我们必然要将区间 [ i , k ] [i,k] [i,k]中的所有数字都选上一遍.如果我们想选一个 i i i此时的 i i i大于 k k k,那么我们必然要将区间 [ k , i ] [k,i] [k,i]的所有数字都选上一遍.那么此时,我们可以将上述的性质总结一下,我们会发现,其实我们就是每次都必须要选一个包含 k k k的子区间.考虑为什么选一个包含 k k k的子区间是必然满足限制的.因为我们是在 k k k左边区间一个 l l l, k k k右边取一个 r r r,然后我们会发现遵循上述的选法,对于一个 i i i小于 k k k,我们多选了一个这个 i i i,必然伴随着 [ i + 1 , k ] [i+1,k] [i+1,k]的所有数字都被选了一遍. i i i大于 k k k同理,所以此时的正确性是对的.

所以我们可以考虑将所有包含k的子区间合并成一个新的物品.然后我们在这些物品中跑一个完全背包即可.

因为大多数的物品合成后的体积都会变得很大,所以实际复杂度很小.


下面是具体的代码部分:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define root 1,n,1
#define ls (rt<<1)
#define rs (rt<<1|1)
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
inline ll read() {
	ll x=0,w=1;char ch=getchar();
	for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') w=-1;
	for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
	return x*w;
}
inline void print(__int128 x){
	if(x<0) {putchar('-');x=-x;}
	if(x>9) print(x/10);
	putchar(x%10+'0');
}
#define maxn 1000000
const double eps=1e-8;
#define	int_INF 0x3f3f3f3f
#define ll_INF 0x3f3f3f3f3f3f3f3f
struct Node{
	int w,v;
}node1[maxn],node2[maxn];int cnt=0;
int sumw[maxn],sumv[maxn];
int dp[maxn];
int main() {
	int n=read();int m=read();int k=read();
	for(int i=1;i<=n;i++) {
		node1[i].w=read();node1[i].v=read();
	}
	for(int i=1;i<=n;i++) {
		sumw[i]=sumw[i-1]+node1[i].w;
		sumv[i]=sumv[i-1]+node1[i].v;
	}
	for(int i=1;i<=k;i++) {
		for(int j=k;j<=n;j++) {
			node2[++cnt].w=sumw[j]-sumw[i-1];
			node2[cnt].v=sumv[j]-sumv[i-1];
		}
	}
	for(int i=1;i<=cnt;i++) {
		for(int j=node2[i].w;j<=m;j++) {
			dp[j]=max(dp[j],dp[j-node2[i].w]+node2[i].v);
		}
	}
	for(int i=1;i<=m;i++) {
		cout<<dp[i]<<" ";
	}
	cout<<endl;
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值