兩回啊兩回...
不妨先將所有線段按照端點從小到大排序,並假設只能往右放,然後強行套上一個dp\(f_{i,j}\)表示放完前\(i\)個線段,最右覆蓋到\(j\)的答案,轉移時枚舉下一條放進來的線段,因爲現在是隻往右放,所以只能去覆蓋\(j\)以後的部分,轉移\(f_{i,j}+min(a_k+l_k-j,l_k)\to 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})\to 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\n",ans);
return 0;
}