(一)思路
题意不再赘述。第一眼就觉得是动态规划。
设(f(i))表示接到第(i)个馅饼能够得到的最大价值。由于一个人每次最多只能移动一格。设上一次接到的馅饼在(p_j),当前要接的馅饼在(p_i),那么,如果能同时接到馅饼(i,j),必要条件是:(|p_i-p_j|leq 2(t_i-t_j),t_i-t_jge0)
因此,状态转移方程可以表示为:
(f(i)=val(i)+sumlimits_{|p_i-p_j|leq 2(t_i-t_j)}f(j))
(val(i))是第(i)个馅饼的价值。
因为:
[|p_i-p_j|leq 2(t_i-t_j)iff egin{cases} 2t_j-p_jleq 2t_i-p_i \ 2t_j+p_jleq 2t_i-p_iend{cases}
]
所以:
[displaystyle f(i)=val(i)+sumlimits_{2t_j-p_jleq 2t_i-p_i wedge2t_j+p_jleq 2t_i+p_i}f(j)
]
(二)实现
不难发现,上面的约束条件其实是一个二维偏序。因此,可以用树状数组/CDQ分治等算法和数据结构水过。由于我过于愚钝,开始没注意到二位偏序的关系,惨遭WA。
数据结构的具体做法是,先按照一维排序,然后再用数据结构维护第二维。
(三)代码
@pythoner713 巨佬强烈要求发代码。
下面是动态开点线段树的代码(因为我懒得写离散化)。
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 5;
inline int read(){
int w = 0, f = 1; char ch = getchar();
while(ch < '0' or ch > '9') {if(ch == '-') f = -f; ch = getchar();}
while(ch >= '0' and ch <= '9') w = w*10 + ch - '0', ch = getchar();
return w*f;
}
struct SegmentTree{
int ls, rs, dat;
}t[maxn*30];
int tot;
void update(int p){
t[p].dat = max(t[t[p].ls].dat, t[t[p].rs].dat);
}
void insert(int &p, int L, int R, int x, int val){
if(!p) p = ++tot;
if(L == R){
t[p].dat = max(t[p].dat, val);
return ;
}
int mid = (L+R)>>1;
if(x <= mid) insert(t[p].ls, L, mid, x, val);
else insert(t[p].rs, mid+1, R, x, val);
update(p);
}
int ask(int p, int L, int R, int l, int r){
if(!p) return 0;
if(l <= L and r >= R){
return t[p].dat;
}
int mid = (L+R)>>1, val = 0;
if(l <= mid) val = max(val, ask(t[p].ls, L, mid, l, r));
if(r > mid) val = max(val, ask(t[p].rs, mid+1, R, l, r));
return val;
}
struct PIE{
int t, p, v;
inline bool operator <(const PIE& x) const{
return 2*t + p < 2*x.t + x.p;
}
}pie[maxn];
int N, W;
signed main(){
tot = 1; int root = 1;
W = read()<<1, N = read();
for(int i=1, x, y, z; i <= N; i++){
x = read(), y = read(), z = read();
pie[i] = (PIE){x, y, z};
}
sort(pie+1, pie+N+1);
for(int i=1; i<=N; i++){
int val = ask(1, -W, W, -W, 2*pie[i].t - pie[i].p) + pie[i].v;
insert(root, -W, W, 2*pie[i].t - pie[i].p, val);
}
cout<<t[1].dat<<endl;
return 0;
}