CF559E Gerald and Path(DP)
设 \(dp(i,p)\) 表示完成前 \(i\) 条线段的覆盖,最右端位于 \(p\) 点的最大收益。
转移?向下一条线段转移时加上他们中间的距离?发现这样没有办法统计 \(p\) 点以前的空位了!
\(\color{yellow}{\bigstar\texttt{Trick}}\):如果出现上面没有办法统计 \(p\) 点以前的空位的情况,说明覆盖 \(p\) 点的这条线段是没有用的,每次只需要考虑有用的线段。
若要枚举有用的线段,从 \(i\) 点枚举下一个有用的线段 \(k\),钦定 \([i+1,k-1]\) 之间的线段都是没有用的,这样就可以像上面一样转移了。
但是,如果没有用的线段中出现一个线段它的右端点超过了 \(k\) 线段的右端点呢?那就再扫过 \([i+1,k-1]\) 线段的时候统计下最右边端,再加上它与 \(k\) 线段右端的贡献即可。
实现的时候记 \(p\) 点只用记录最右端线段编号和朝向即可,设 \(dp(i,j,p)\) 表示到 \(i\) 线段为止,最右边的线段是 \(j\),它的朝向为 \(p\) 的最大收益。
\[dp(k,x,y)\leftarrow dp(i,j,p)+\min(r_k-p,len_k)+r_x-r_k
\]
#define Maxn 105
int n,ans;
int dp[Maxn][Maxn][2];
struct Seg
{
int pos,len;
Seg(int Pos=0,int Len=0):pos(Pos),len(Len){}
inline bool friend operator < (Seg x,Seg y) { return x.pos<y.pos; }
}s[Maxn];
int main()
{
n=rd();
for(int i=1,p,l;i<=n;i++) p=rd(),l=rd(),s[i]=Seg(p,l);
sort(s+1,s+n+1),s[0].pos=-inf;
for(int i=0;i<=n;i++)
for(int j=0;j<=i;j++) for(int p=0;p<2;p++)
{
int rn=s[j].pos+p*s[j].len,maxx=-inf,pos=-1,d;
ans=max(ans,dp[i][j][p]);
for(int k=i+1;k<=n;k++) for(int q=0;q<2;q++)
{
int cur=s[k].pos+q*s[k].len;
if(cur>=maxx) maxx=cur,pos=k,d=q;
dp[k][pos][d]=max(dp[k][pos][d],
dp[i][j][p]+min(cur-rn,s[k].len)+maxx-cur);
}
}
printf("%d\n",ans);
return 0;
}