题意:
给你\(N\)个物品,每个物品有其价格 $ P_i $,之前必须要买的物品价格和 $ L_i $,之后必须要买的物品价格和 $ R_i $ 和价值 $ W_i $ 。试给出一种物品的选择方案,使得满足所有选择的物品的条件且选择物品的价值和最大(物品的选择顺序必须要与原来的顺序相同)。\(N \leq 10^5 , P , L , R \leq 10^5 , W \leq 10^4\)
像背包DP,所以就是背包DP(雾)
我们能够发现从物品 $ i $ 转移到物品 $ j $ 的充要条件是: $ R_i=R_j+P_j $ 且 $ L_i+P_i=L_j $。将二式相加得 $ P_i+L_i+R_i=P_j+L_j+R_j $,也就是说 $ P+L+R $相等的物品才能够互相转移。所以我们可以考虑使用 $ vector $存每个 $ P + L + R $ 对应的物品,对于每个组跑一次DP。因为每一个物品的转移只能从 $ L $ 到 $ L + P $ ,所以转移是 $ O ( 1 ) $ 的,所以DP总复杂度为 $ O( n ) $ 。注意DP数组的清空推荐使用还原而不是memset,这样还原的复杂度才是\(O(n)\)。
获得了最大的价值之后,对对应的那一个组别再跑一遍DP,跑出方案。方案的记录可以通过记录某一个物品选择前最后选择的物品来实现。总复杂度为\(O(n)\)。
注意每一个组别一定要有始有终(也就是选择的物品必须要有 $ L=0 $ 与 $ R=0 $ 的物品,也可以通过这一个来剪一些枝降低常数)
关于输出方案其实可以使用递归,但是因为本机会爆栈所以用循环+vector输出
CODE:
#include<bits/stdc++.h>
#define MAXN 3000010
#define MAXM 200010
using namespace std;
inline int read(){
int a = 0;
char c = getchar();
while(!isdigit(c))
c = getchar();
while(isdigit(c)){
a = (a << 3) + (a << 1) + (c ^ '0');
c = getchar();
}
return a;
}
struct thing{
int w , p , l , r , ind;
}now;
vector < thing > v[MAXN];
vector < int > anss;
int maxPri[MAXN] , last[MAXM] , k[MAXN] , cnt[MAXN] , ans[MAXN] , maxN = -1 , maxDir;
bool haveEnd[MAXN];
void out(int dir){
while(dir != -1){
anss.push_back(v[maxDir][dir].ind);
dir = last[dir];
}
for(int i = anss.size() - 1 ; i >= 0 ; i--)
printf("%d " , anss[i]);
}
int main(){
int N = read();
for(int i = 1 ; i <= N ; i++){
now.w = read();
now.p = read();
now.l = read();
now.r = read();
now.ind = i;
if(cnt[now.p + now.l + now.r] || now.l == 0){
cnt[now.p + now.l + now.r]++;
v[now.p + now.l + now.r].push_back(now);
if(now.r == 0)
haveEnd[now.p + now.l + now.r] = 1;
}
}
memset(maxPri , -0x3f , sizeof(maxPri));
maxPri[0] = 0;
for(int i = 0 ; i <= 3000000 ; i++)
if(cnt[i] && haveEnd[i]){
for(int j = 0 ; j < cnt[i] ; j++)
maxPri[v[i][j].l + v[i][j].p] = max(maxPri[v[i][j].l + v[i][j].p] , maxPri[v[i][j].l] + v[i][j].w);
if(maxN < maxPri[i]){
maxN = maxPri[i];
maxDir = i;
}
for(int j = 0 ; j < cnt[i] ; j++)
maxPri[v[i][j].l + v[i][j].p] = -0x3f3f3f3f;
}
memset(last , -1 , sizeof(last));
memset(k , -1 , sizeof(k));
for(int j = 0 ; j < cnt[maxDir] ; j++)
if(maxPri[v[maxDir][j].l + v[maxDir][j].p] < maxPri[v[maxDir][j].l] + v[maxDir][j].w){
maxPri[v[maxDir][j].l + v[maxDir][j].p] = maxPri[v[maxDir][j].l] + v[maxDir][j].w;
last[j] = k[v[maxDir][j].l];
k[v[maxDir][j].l + v[maxDir][j].p] = j;
ans[v[maxDir][j].l + v[maxDir][j].p] = ans[v[maxDir][j].l] + 1;
}
printf("%d\n" , ans[maxDir]);
out(k[maxDir]);
return 0;
}