把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【CF559E】Gerald and Path(动态规划)

点此看题面

  • 数轴上有\(n\)条线段,给出每条线段一个端点和长度。
  • 求它们能覆盖的总长度的可能最大值。
  • \(n\le100\)

动态规划

基本上自己推出来的吧,可惜有一步明明自己想出来了却用一个假例子把自己\(Hack\)掉了。。。

显然先离散化,并将线段按端点排序。然后考虑设\(f_{i,j}\)表示选取前\(i\)条线段覆盖的最右边界为\(j\)时的最大覆盖长度。

为了防止重复的位置计算两次,对于一条线段\([l,r]\),我们可以把它当作\([l,l],[l,l+1],...,[l,r]\)各统计一遍答案,这应该是比较容易想到的。

一条线段无非两种情况,向左或者向右。

如果向右,那么它显然不会影响到之前的线段,可以直接转移。

如果向左,主要是考虑或许之前会有一条线段向右超出当前点。则我们枚举一个分界点,分界点左侧任选且假设不会超出右端点,右侧强制向右求出最右的位置,然后转移。

考虑向左的情况如果存在反例,必然是令分界点左侧一个点向右、分界点右侧一个点向左会得到最优解。但实际上如果此时我们让分界点左侧的点向左、分界点右侧的点向右一定会更优,也就是说这样的反例不可能存在,因此正确性得证。

代码:\(O(n^3)\)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100
#define Gmax(x,y) (x<(y)&&(x=(y)))
#define Gmin(x,y) (x>(y)&&(x=(y)))
using namespace std;
int n,dc,dv[3*N+5],f[N+5][3*N+5],Mx[N+5][N+5];
struct Data {int x,l;I bool operator < (Con Data& o) Con {return x<o.x;}}s[N+5];
int main()
{
	RI i,j,k,x,y,z;for(scanf("%d",&n),i=1;i<=n;++i) scanf("%d%d",&s[i].x,&s[i].l),
		dv[++dc]=s[i].x,dv[++dc]=s[i].x-s[i].l,dv[++dc]=s[i].x+s[i].l;sort(s+1,s+n+1);
	#define GV(x) (lower_bound(dv+1,dv+dc+1,x)-dv)
	for(sort(dv+1,dv+dc+1),dc=unique(dv+1,dv+dc+1)-dv-1,i=1;i<=n;++i)
		for(Mx[i][i]=GV(s[i].x+s[i].l),j=i+1;j<=n;++j) Mx[i][j]=max(Mx[i][j-1],GV(s[j].x+s[j].l));//预处理区间最右端点
	for(i=1;i<=n;++i)//动态规划
	{
		#define T(j,l,r) for(k=r;k>=l;--k) Gmax(f[i][k],f[j][l]+dv[k]-dv[l])//从第j条线段转移区间[l,r]
		x=GV(s[i].x);T(i-1,x,GV(s[i].x+s[i].l));//向右
		for(y=GV(s[i].x-s[i].l),j=1;j<=i;++j) T(j-1,y,max(x,Mx[j][i-1]));//向左,枚举分界点
		for(j=1;j<=dc;++j) Gmax(f[i][j],f[i-1][j]),Gmax(f[i][j],f[i][j-1]);//注意做前缀最大值
	}return printf("%d\n",f[n][dc]),0;
}
posted @ 2020-11-23 21:22  TheLostWeak  阅读(160)  评论(0编辑  收藏  举报