[CF559E] Gerald and Path
\(\text{Problem}:\)Gerald and Path
\(\text{Solution}:\)
首先将所有点按给定的端点排序,下面的线段标号为排序后的重标号。
设 \(f_{i,j,0/1}\) 表示当前考虑到第 \(i\) 个线段,右端点最右边的线段为 \(j\),此端点是否为给定的端点的最大覆盖长度。考虑向外转移,设枚举到第 \(k\) 个线段的方向为 \(t\),只需记录 \([i+1,k]\) 中所有可能右端点最大的线段 \(p\),从 \(f_{i,j,0/1}\) 转移到 \(f_{k,p,t}\),并加上 \(i,k,p\) 三条线段可能产生的新贡献即可。
乍一看这个转移漏洞重重,但实际上非常高妙。考虑为什么上述情况是可以得到最优转移的。
若存在线段 \(p\) 的左右端点分别比 \(k\) 的左右端点小,但 \(p>k\),那么关于 \(i,p,k\) 的最优转移会在 \(i\rightarrow p\) 时取到;反之同理。而只算 \(i,p,k\) 的新增贡献,忽视区间 \((i,k)\) 中其他对答案也右贡献的线段的正确性,可以考虑画图举例,若还有其他对答案有贡献的线段,则会成为一个子问题被解决。于是两个关于转移较大的疑点的正确性就得到了保证。
总时间复杂度 \(O(n^3)\)。
\(\text{Code}:\)
#include <bits/stdc++.h>
//#pragma GCC optimize(3)
//#define int long long
#define ri register
#define mk make_pair
#define fi first
#define se second
#define pb push_back
#define eb emplace_back
#define is insert
#define es erase
#define vi vector<int>
#define vpi vector<pair<int,int>>
using namespace std; const int N=110;
inline int read()
{
int s=0, w=1; ri char ch=getchar();
while(ch<'0'||ch>'9') { if(ch=='-') w=-1; ch=getchar(); }
while(ch>='0'&&ch<='9') s=(s<<3)+(s<<1)+(ch^48), ch=getchar();
return s*w;
}
int n,f[N][N][2],ans;
struct Node { int x,l; }a[N];
inline bool cp(Node a,Node b) { return a.x==b.x?a.l<b.l:a.x<b.x; }
signed main()
{
n=read();
for(ri int i=1;i<=n;i++) a[i].x=read(), a[i].l=read();
sort(a+1,a+1+n,cp);
a[0].x=-1e9;
for(ri int i=0;i<n;i++)
{
for(ri int j=0;j<=i;j++)
{
for(ri int k=0;k<2;k++)
{
int mx=-1e9,p,t;
int nr=a[j].x+k*a[j].l;
for(ri int x=i+1;x<=n;x++)
{
for(ri int y=0;y<2;y++)
{
int xr=a[x].x+y*a[x].l;
if(mx<xr) mx=xr, p=x, t=y;
f[x][p][t]=max(f[x][p][t],f[i][j][k]+mx-xr+min(xr-nr,a[x].l));
ans=max(ans,f[x][p][t]);
}
}
}
}
}
printf("%d\n",ans);
return 0;
}
夜畔流离回,暗叹永无殿。
独隐万花翠,空寂亦难迁。
千秋孰能为,明灭常久见。
但得心未碎,踏遍九重天。