CI.[IOI2009]salesman
思想非常simple:因为一次从上游往下游的转移,可以被表示成
\(f_i+(pos_i-pos_j)\times U\rightarrow f_j\ |\ pos_i<pos_j\land tim_i<tim_j\)
拆开括号,即可得到两半互不相关的部分。然后直接使用线段树/树状数组进行转移即可。
从下游往上游的转移也可以类似地处理。
现在考虑\(tim\)中可能有相等的情形,并不能确定访问顺序。这个再使用一遍辅助DP过一遍就行了。有一个结论是当\(tim\)相等时,一次转移中一定不会走回头路——回头路的部分完全可以在上次转移和下次转移处就处理掉了。然后就直接DP过就行了。
3min就能想出的idea,我整整调了3d。主要因为一开始套了两重离散化,后来发现数据范围开的下便删去了离散化;一开始写的是线段树,后来发现线段树debug起来很麻烦,便换成了BIT;一开始也没有想到没有回头路的情形,辅助DP时写的极其憋屈(后来证明就是这个憋屈的DP中有一处\(U\)和\(D\)写反了);同时中文题面翻译还翻译错了,这个“距离”是到上游的距离而非到下游的距离。于是种种因素叠加在一起,debug得精神崩溃。
代码:
#include<bits/stdc++.h>
using namespace std;
const int inf=0xc0c0c0c0;
const int N=1001000;
int n,U,D,S,tim[N],pos[N],bon[N],m=500100,ord[N],f[N],g[N],upper[N],lower[N];//upper:the maximal when go against the wave; lower:vice versa
void modify(int P,int val){
for(int x=P;x;x-=x&-x)upper[x]=max(upper[x],val-P*U);
for(int x=P;x<=m;x+=x&-x)lower[x]=max(lower[x],val+P*D);
}
int queryupper(int P){
int ret=inf;
for(int x=P;x<=m;x+=x&-x)ret=max(ret,upper[x]+P*U);
return ret;
}
int querylower(int P){
int ret=inf;
for(int x=P;x;x-=x&-x)ret=max(ret,lower[x]-P*D);
return ret;
}
#define I ord[i]
#define J ord[j]
#define K ord[k]
int main(){
scanf("%d%d%d%d",&n,&U,&D,&S),memset(upper,0xc0,sizeof(upper)),memset(lower,0xc0,sizeof(lower));
for(int i=1;i<=n;i++)scanf("%d%d%d",&tim[i],&pos[i],&bon[i]),ord[i]=i;
sort(ord+1,ord+n+1,[](int x,int y){return tim[x]==tim[y]?pos[x]<pos[y]:tim[x]<tim[y];});
modify(S,0);
for(int i=1,j=1;j<=n;){
while(tim[I]==tim[J])f[J]=g[J]=max(queryupper(pos[J]),querylower(pos[J]))+bon[J],j++;
for(int k=i+1;k<j;k++)f[K]=max(f[K],f[ord[k-1]]-(pos[K]-pos[ord[k-1]])*D+bon[K]);
for(int k=j-2;k>=i;k--)g[K]=max(g[K],g[ord[k+1]]-(pos[ord[k+1]]-pos[K])*U+bon[K]);
while(i<j)modify(pos[I],max(f[I],g[I])),i++;
}
printf("%d\n",max(queryupper(S),querylower(S)));
return 0;
}