[NAIPC2016]Jewel Thief(决策单调性+分治)
题面
有(n)个物品,每个物品有一个体积(w_i)和价值(v_i),现在要求对(V in [1,m]),求出体积为(V)的
背包能够装下的最大价值(1 ≤ n ≤ 1000000; 1 ≤ m ≤ 100000; 1 ≤ w_i ≤ 300; 1 ≤ v_i ≤ 10^9)
分析
决策单调性发现
注意到物品的体积很小,考虑按体积分类,选取同种体积的物品时,一定优先选择价值大的物品。
设(dp[i][j])为使用前i种体积的物品,体积为j的最大价值。类似多重背包的单调队列优化,将模i同余的所有位置拿出来重新标号(即下标看作1,2,3....x)。
则有
其中(w(k,j))表示第(i)种体积的物品中,最大的(j-k)个的价值和
显然(val)满足四边形不等式。
问题转化
令(A)为一个矩阵,(pos(j)=max({i|,forall p in [0,m],A[i][j]>A[p][j] })),即使得第(j)列第(i)行的值最大的(i)(如果有多个i相同,则取编号最大的)
在原问题中,考虑第(i-1)层到第(i)层的转移 ,令(A[k][j]=egin{cases} dp[i-1][k]+val(k,j),k leq j \ -infty,k>jend{cases}),我们发现dp的转移实际上就是在求第(k)行第(j)列的最大值,其中(j)固定。那么(dp[i][j]=A[pos(j)][j])
于是问题就转化为:已知一个((m+1) imes(m+1))大小的矩阵(A),其中每个元素的值均可以在(O(1))时间内查询。现
要求对于(j in [0,m]),求出(A[pos(j)][j])
分治求解
对列[l,r]进行分治,维护当前可能成为(pos)的行([x,y]),令(mid=frac{l+r}{2}),暴力枚举所有可能的行求出(pos(mid)),分治递归操作([l,mid-1] [x,pos(mid)])以及([mid +1,r] [pos(mid),y])直至(l = r)或(x = y)。容易发现这样的时间复杂度是(O(mlog m))
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<algorithm>
#define maxw 300
#define maxn 1000000
using namespace std;
typedef long long ll;
vector<ll>w[maxw+5];
ll dp[2][maxn+5];
int n,m;
void divide(int l,int r,int x,int y,int now,int mod,int rest){
//列[l,r],行[x,y];
if(l>r) return;
int mid=(l+r)>>1,pos=mid;
dp[now^1][mid*mod+rest]=dp[now][mid*mod+rest];
for(int j=min(y,mid-1);j>=x;j--){//枚举可能成为pos(mid)的列,注意j<mid
if(mid-j>(int)w[mod].size()) break;
if(dp[now][j*mod+rest]+w[mod][mid-j-1]>dp[now^1][mid*mod+rest]){
dp[now^1][mid*mod+rest]=dp[now][j*mod+rest]+w[mod][mid-j-1];
pos=j;
}
}
divide(l,mid-1,x,pos,now,mod,rest);
divide(mid+1,r,pos,y,now,mod,rest);
}
inline int cmp(int x,int y){
return x>y;
}
int main(){
int x,y;
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++){
scanf("%d %d",&x,&y);
w[x].push_back(y);
}
for(int i=1;i<=maxw;i++){
sort(w[i].begin(),w[i].end(),cmp);
for(int j=1;j<(int)w[i].size();j++) w[i][j]+=w[i][j-1];
}
int now=0;
for(int i=1;i<=300;i++){
if(w[i].size()){
for(int j=0;j<i;j++){
//将模i同余的所有位置拿出来
divide(0,(m-j)/i,0,(m-j)/i,now,i,j);
}
for(int j=1;j<=m;j++){
dp[now^1][j]=max(dp[now^1][j],dp[now^1][j-1]);
//我们dp的子状态是体积<=j,而分治过程中是=j
}
now^=1;
}
}
for(int i=1;i<=m;i++) printf("%I64d ",dp[now][i]);
}