猪宝

该博客讨论了一种动态规划方法来解决卡车运输小猪的问题。每只小猪有不同的重量和价值,需要在不超过卡车最大载重的限制下,求出最大总价值。通过按重量分组、前缀和和模运算优化,实现了O(cklogk)的时间复杂度解决方案。

题目

传送门 to LOJ

题目描述
n n n 只小猪。第 i i i 只小猪重 C i    kg C_i\;\text{kg} Cikg ,能卖 V i V_i Vi 元的好价钱。

现在问题是:如果卡车的最大载重是 j    kg j\;\text{kg} jkg ,那么最多能卖多少钱?

对于每个 1 1 1 k k k 之间的 j j j ,都请输出答案。

数据范围与提示
n ≤ 1 0 6 n\le 10^6 n106 k ≤ 5 × 1 0 4 k\le 5\times10^4 k5×104 C i ≤ 300 C_i\le 300 Ci300 V i ≤ 1 0 9 V_i\le 10^9 Vi109

思路

直接背包。首先按照 C i C_i Ci 分组,然后对于某个 c c c ,用 v x v_x vx 表示前 x x x 大的求和,有转移

f ( i ) = max ⁡ x ≡ i ( m o d c ) ,    x ≤ i f ( x ) + v i − x c f(i)=\max_{x\equiv i\pmod{c},\;x\le i}f(x)+v_{\frac{i-x}{c}} f(i)=xi(modc),ximaxf(x)+vcix

可以按照模 c c c 的值分类。而后你会发现 决策点单调性,因为 v x v_x vx 斜率单减。

然后就做完了。复杂度 O ( c k log ⁡ k ) \mathcal O(ck\log k) O(cklogk)

代码

#include <cstdio>
#include <iostream>
#include <vector>
#include <algorithm>
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 MaxC = 305;
const int MaxK = 50005;
int n, k; // basic info

int_ dp[MaxK], tmp[MaxK];
int_ val[MaxK]; // 获得的价值
inline int split(int c,int i,int j){
	int L = 0, R = (k-j)/c+1;
	while(L != R){
		int m = (L+R)>>1;
		if(val[(j-i)/c+m]+dp[i]
		> val[m]+dp[j]) L = m+1;
		else R = m; // 二分
	}
	return j+c*L; // j 的开始掌控位置
}

vector< int > v[MaxC];
struct Node{
	int id, l;
};
Node q[MaxK]; // 模拟队列
int head, tail;
int main(){
	n = readint(), k = readint();
	for(int i=1; i<=n; ++i){
		int c = readint();
		v[c].push_back(readint());
	}
	Node t; // 没啥用的
	for(int i=1; i<MaxC; ++i){
		sort(v[i].begin(),v[i].end(),
			greater<int>());
		int len = v[i].size();
		if(len > k/i) // 不然爆体积
			v[i].resize(len = k/i);
		for(int j=1; j<=len; ++j)
			val[j] = val[j-1]
				+ v[i][j-1]; // 前缀和
		for(int j=len+1; j<=k/i; ++j)
			val[j] = val[len]; // 补全
		for(int r=0; r<i; ++r){ // 余数
			head = 0, tail = -1; // 清空队列
			for(int j=r; j<=k; j+=i){
				t.id = j, t.l = 0;
				while(head <= tail){
					t.l = split(i,q[tail].id,j);
					if(t.l <= q[tail].l)
						-- tail; // pop_back
					else break; // 无法更新
				}
				q[++ tail] = t;
				while(head+1 <= tail &&
				q[head+1].l <= j) ++ head;
				tmp[j] = dp[q[head].id]
					+ val[(j-q[head].id)/i];
			}
		}
		for(int j=1; j<=k; ++j)
			dp[j] = tmp[j];
	}
	for(int i=1; i<=k; ++i)
		printf("%lld ",dp[i]);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值