[CF1132E]Knapsack

题目

传送门 to luogu

思路

直接背包肯定是不行滴,背包容量都快到 l o n g    l o n g \tt long\; long 极限了。除非你有欧洲血统。

去网上搜搜题解,发现了爆搜做法,并表示很淦。再找找,发现了合理的背包做法。然后把他俩的结合起来,得到了当前做法。

给出如下性质:从轻到重考虑,在能够选择的前提下,每个物品最多有 8 8 个不选

为啥?可以这样考虑,假如我一股脑塞进背包,可能不是最优情况,因为背包的一部分剩余空间没有被充分利用。比如,在重量为 b b 时,背包已经只剩下不到 b b 的空间了。

假如要放入重量为 a a 的物品 x x 个,拿出重量为 b b 的物品 y y 个,导致背包总使用量增大 c ( c < 8 ) c(c<8) ,其实就是方程

a x b y = c ax-by=c

改成同余方程组就是

b y c ( m o d a ) -by\equiv c\pmod{a}

显然在 y [ 0 , a ) y\in[0,a) 时, b y -by 可以取遍所有可能的余数。即:若方程有解,则存在一解使得 0 y < a 0\le y<a

根据上一条推出 b y < a b by<ab ,故 a x = c + b y < a + a b = a ( b + 1 ) ax=c+by<a+ab=a(b+1) ,所以 x < b + 1 x<b+1 x b x\le b

此时我们已证 x b 8 x\le b\le 8 y < a 8 y<a\le 8 。故,在 8 8 个物品以内的调整,就足以满足所有要求。所以我之前的每种重量留下 8 8 个备用即可!不到 8 8 个?我全都留下来呗,你还要我怎样?

除了备用重量,其他的一股脑往背包里塞,求出剩余容量,然后对剩余物品进行背包。显然剩余容量最多用到 i = 1 8 8 i = 288 \sum_{i=1}^{8}8i=288 ,复杂度完全不用担心呢,即使不用单调队列(或二进制分组)优化,也可以很快的跑过!

代码

#include <cstdio>
#include <iostream>
#include <vector>
using namespace std;
typedef long long int_;
inline int_ readint(){
	int_ a = 0; char c = getchar(), f = 1;
	for(; c<'0'||c>'9'; c=getchar())
		if(c == '-') f = -f;
	for(; '0'<=c&&c<='9'; c=getchar())
		a = (a<<3)+(a<<1)+(c^48);
	return a*f;
}

const int MaxL = 8; // 剩下几个
const int MaxW = MaxL*(8*9>>1);
bool dp[MaxW+1]; // 剩余容量的背包
long long w, ans, cnt[9];

int main(){
	w = readint();
	for(int i=1; i<9; ++i){
		cnt[i] = readint();
		if(cnt[i]*i > w)
			cnt[i] = w/i;
		long long lqy = cnt[i]-MaxL;
		if(lqy < 0) lqy = 0;
		ans += lqy*i, w -= lqy*i;
		cnt[i] -= lqy;
	}
	dp[0] = true; if(w > MaxW) w = MaxW;
	for(int i=1; i<=8; ++i)
	for(int j=w; j; --j) if(!dp[j])
	for(int k=1; k*i<=j&&k<=cnt[i]; ++k)
		if(dp[j-k*i]){ dp[j] = 1; break; }
	for(int j=w; ~j; --j) if(dp[j]){
		printf("%lld",ans+j); break;
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_42101694/article/details/107906936
今日推荐