LOJ 6039 珠宝

LOJ #6039 珠宝

题意:

01背包

数据范围:

\(1 \leq N \leq 1000000, 1 \leq K \leq 50000, 1 \leq C_i \leq 300, 0 \leq V_i \leq 10 ^ 9\)

题解:

考虑每个物品的代价不同的只有不多于 \(300\) 个,把相同代价的物品分成一类同时进行 \(dp\)

先考虑相同代价的物品价值都相同,这就是一个单调队列优化多重背包,把这种思想类比到价值不同的情况下。

根据贪心,相同代价时肯定优先选择价值高的物品,设 \(w_{i,j}\) 为取 \(j\) 个代价为 \(i\) 的最大价值,显然 \(w_{i,j}\) 为把所有代价为 \(i\) 的物品价值按从大到小排序后的前缀和。

\(dp_{i,j}\) 为只选了代价小于等于 \(i\) 的物品且总代价小于等于 \(j\) 的最大价值,显然我们有转移方程为 \(dp_{i,j}=\max(dp_{i-1,j-ki}+w_{i,j})\)

但是可以发现 \(w_i\) 是一个凸函数,并不能直接用单调队列转移(可能后来居上,但单调队列并没有反应)。可以再维护一个 \(T\)\(T_i\)\(i\) 最早被单调队列中下一个决策更劣的时间,可以二分找。

每次找决策点时,看一看队首是否已经不是最优。插入一个决策点时,如果当前队尾比队尾前一个更优的时间大于等于队尾比新决策点更劣的时间,则这个队尾决策一定不会被用到,直接弹出。

复杂度为 \(\mathcal{O}(\max(C_i)K\log N)\)

代码实现:

#include<bits/stdc++.h>
#define int long long
using namespace std;
int q[100005],h,t;
int f[50005],g[50005],T[50005],n,m;
vector<int>w[305];
int gettime(int l,int r,int x,int s) {
	int L=r+1,R=l+w[x].size(),ans=l+w[x].size()+1;
	while(L<=R) {
		int mid=(L+R)>>1;
		if(f[r*x+s]+w[x][mid-r-1]<f[l*x+s]+w[x][mid-l-1]) L=mid+1;
		else ans=mid,R=mid-1;
	} return ans;
}
void DP(int x) {
#define I (i*x+s)
	memset(T,0x3f,sizeof(T));
	memset(g,0,sizeof(g));
	for(int s=0;s<x;s++) {
		q[h=t=1]=0; g[s]=f[s];
		for(int i=1;I<=m;i++) {
			while(h<=t&&T[q[h]*x+s]<=i) h++; g[I]=max(f[q[h]*x+s]+w[x][i-q[h]-1],f[I]);
			while(h<t&&T[q[t-1]*x+s]>=gettime(q[t],i,x,s)) t--;
			T[q[t]*x+s]=gettime(q[t],i,x,s); q[++t]=i; T[I]=0x3f3f3f3f;
		}
	} for(int i=1;i<=m;i++) f[i]=max(g[i],f[i]);
#undef I
}
signed main() {
	scanf("%lld%lld",&n,&m);
	for(int i=1,s,v;i<=n;i++) scanf("%lld%lld",&s,&v),w[s].push_back(v);
	for(int i=1;i<=300;i++) {
		if(!w[i].size()) continue ;
		sort(w[i].begin(),w[i].end());
		reverse(w[i].begin(),w[i].end());
		for(int j=1;j<w[i].size();j++) w[i][j]+=w[i][j-1];
		DP(i);
	} for(int i=1;i<=m;i++) printf("%lld ",f[i]);
}
posted @ 2020-12-29 23:14  Cheng_yf  阅读(135)  评论(0)    收藏  举报