两回啊两回...
不妨先将所有线段按照端点从小到大排序,并假设只能往右放,然后强行套上一个dp(f_{i,j})表示放完前(i)个线段,最右覆盖到(j)的答案,转移时枚举下一条放进来的线段,因为现在是只往右放,所以只能去覆盖(j)以后的部分,转移(f_{i,j}+min(a_k+l_k-j,l_k) o f_{k,max(a_k+l_k,j)})
然后这题里面现在是可以往左放的.如果当前线段往右放,那么因为放过的线段都在它的前面,所以它覆盖的一定是当前的最右端(j)以后的位置.但是如果往左放,可能会出现覆盖的新段有多个段的问题.为了避免这种情况,因为前面我们是枚举在(i)后面的一个线段(k),那么先不考虑(i,k)之间的线段,只考虑(k)覆盖左边以及前面覆盖最右端(j)的影响,这里贡献为(min(a_k-j,l_k)).然后对于(i,k)之间的线段,我们将他们全部向右放,并考虑贡献,如果这一些线段最右端为(mx),那么这里贡献为(mx-a_k)
至于为什么这样子是对的,首先可以发现这样子转移贡献一定不会算多,因为每次覆盖的地方都是之前没覆盖过的地方;然后注意到(k)往左放的时候,可能到达的最左端点比当前(i)的左端点还要左,但并没有考虑(i)左端点以左的部分,那如果出现这种情况,其实(i)左端点以左的贡献可以在(i)前面的位置(i')被统计,而在(i')处(i)也就是有一个往右贡献;至于在(i')处转移时(i)往左更优怎么办,那就会先统计(i)往左的贡献,然后在(i)处转移是统计(k)的贡献,因为(i)往左更优,说明(k)往左不会盖到(i)线段左端点
形式化的,下面的状态为(f_{i,j,k(0/1)})表示前(i)个线段,覆盖到最右端的线段为(j),方向朝左/朝右,这样我们可以把某条线段(x)的右端点表示为(a_x+k*l_x).然后转移的时候记录枚举过的线段的最右的(j,k),记为(nj,nk),然后当前枚举线段(x)方向(p),转移可以写成
(f_{i,j,k}+min((a_{x}+p*l_{x})-(a_{j}+k*l_{j}),l_x)+(a_{nj}+nk*l_{nj})-(a_{x}+p*l_{x}) o f_{x,nj,nk})
#include<bits/stdc++.h>
#define LL long long
#define db double
using namespace std;
const int N=100+10,M=205,mod=1e9+7;
int rd()
{
int x=0,w=1;char ch=0;
while(ch<'0'||ch>'9'){if(ch=='-') w=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+(ch^48);ch=getchar();}
return x*w;
}
struct node
{
int x,y;
bool operator < (const node &bb) const {return x<bb.x;}
}a[N];
int n,f[N][N][2];
int ps(int x,int y){return a[x].x+a[x].y*y;}
int main()
{
n=rd();
for(int i=1;i<=n;++i) a[i].x=rd(),a[i].y=rd();
a[++n]=(node){-(int)2e8,0};
sort(a+1,a+n+1);
memset(f,-0x3f3f3f,sizeof(f));
f[1][1][0]=0;
int ans=0;
for(int i=1;i<=n;++i)
for(int j=1;j<=i;++j)
for(int k=0;k<=1;++k)
{
ans=max(ans,f[i][j][k]);
int nj=j,nk=k;
for(int l=i+1;l<=n;++l)
for(int p=0;p<=1;++p)
{
if(ps(nj,nk)<=ps(l,p)) nj=l,nk=p;
f[l][nj][nk]=max(f[l][nj][nk],f[i][j][k]+min(ps(l,p)-ps(j,k),a[l].y)+ps(nj,nk)-ps(l,p));
}
}
printf("%d
",ans);
return 0;
}