两回啊两回...
不妨先将所有线段按照端点从小到大排序,并假设只能往右放,然后强行套上一个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;
}
原文:https://www.cnblogs.com/smyjr/p/12288586.html