区间DP(一)

还是刷了一些题,总结一下吧。

枚举顺序

AT_arc108_e

fl,r 表示选择了 l,r 之后的期望长度。转移上枚举下一个数是谁,有方程:

fl,r=1+1cntl<i<r,alaiarfl,i+fi,r

拆开来有:

fl,r=1+1cnt(l<i<r,alaiarfl,i+l<i<r,alaiarfi,r)

有一种妙的枚举顺序,左端点从大到小,右端点从小到大,这样一来就可以用树状数组来维护了。统计答案可以用哨兵元素,复杂度 O(N2logN)

可以推广,也就是说我们的枚举顺序应该服务于我们的转移顺序。另一个经典的例子是 邮局,根据四边形不等式,c(x,y1)c(x,y)c(x+1,y) 的,也就说我们希望 (x+1,y),(x,y1) 要先于 (x,y) 计算,同时由于 DP 自己的需要 (xk,y1) 也要先行计算,所以我们确定出了先按 y 从小到大,然后按 x 从大到小的枚举顺序,这样就可以满足各方的需求了。

P2339

一道应该挺经典的题目。从大区间往小区间转移即可,复杂度平方。

状态设计

这是这几道题做下来的最深的感悟。总结起来就是说,如果一个状态发现无法正确转移,那就说明缺点什么;至于缺点什么,可以从决策过程来思考。在区间 DP 中,缺的这个东西常常是值。然后以几道题为例子说一下。

一个结论是,如果确定了一个题是区间 DP,那么就可以通过数据规模来猜。1000 应该是决策很少的傻逼题或者数据结构等优化的巧妙题,300 应该就是常规的 O(N3) 的 DP,100 大概就是要加点状态了,50 的话如果出题人神志正常的话那么肯定就要加个一两维状态了。

P3607

首先有个思路是把一个子序列翻转等同于选出一些位置对,当然了这些位置对需要形成一个套娃的关系,并交换对应位置对上的数。这很好理解。既然是套娃的关系,那么似乎就可以用区间 DP 来解决了。

朴素地用 fl,r 代表区间 [l,r] 的数已经翻转完毕了的答案。当前决策无非两种,交换 al,ar 和不交换。然而发现两种决策似乎都没法计算贡献,毕竟你不知道中间那块究竟形成了一个怎样的不降序列,所以可以用最经济实惠的方式,也就是记录上下界,来定义状态。于是 fl,r,x,y 代表区间 [l,r] 形成了一个第一个数不小于 x,最后一个数不大于 y 的最大答案。基础转移就是从同区间小值域,或者从同值域小区间转移,不基础的就是考虑 alar 的贡献。此时问题就显得非常简单了,就没必要多说了。代码:

read(m);
for(int i=1;i<=m;i++){
	read(a[i]);
	for(int j=1;j<=a[i];j++)for(int k=a[i];k<N;k++)f[i][i][j][k]=1;
}
for(int len=1;len<m;len++)for(int l=1;l+len<=m;l++){
	int r=l+len;
	for(int y=1;y<N;y++)for(int x=y;x;x--){
		check(f[l][r][x][y],f[l+1][r][x][y]+(a[l]==x));
		check(f[l][r][x][y],f[l][r-1][x][y]+(a[r]==y));
		check(f[l][r][x][y],f[l+1][r-1][x][y]+(a[l]==y)+(a[r]==x));
		check(f[l][r][x][y],f[l][r][x+1][y]);
		check(f[l][r][x][y],f[l][r][x][y-1]);
	}
}
printf("%d\n",f[1][m][1][N-1]);

P4766

也是一个非常巧妙的题目。首先粗略地用 fl,r 定义 [l,r] 的答案,然后发现似乎没法合并,因为假如粗略的把外星人分成 [l,k][k+1,r] 两类分别消灭的话,那么那些时间横跨中点的外星人可能就会被消灭两次,显然不优。于是从合并的角度来看,会发现有一个外星人比较特殊,那就是这个区间中距离最远的那个。这个外星人会被消灭,并且被消灭时使用的代价肯定是它的距离。既然如此,我们不妨钦定一开始就消灭这个外星人,然后考虑剩下的外星人如何处理。枚举消灭外星人王的位置 t,那么显然凡是生命值和 t 有交的外星人都会被消灭,于是剩下的都是在 t 之前死掉的和在 t 之后出生的。在这个时候可以修改一下定义,定义 fl,r 表示消灭完全处在 [l,r] 中的外星人需要的代价,就是顺着上面那种方式转移即可。

void solve(){
	read(m);n=0;
	for(int i=1;i<=m;i++){
		read(a[i].l);read(a[i].r);read(a[i].d);
		b[++n]=a[i].l,b[++n]=a[i].r;
	}
	sort(b+1,b+n+1);n=unique(b+1,b+n+1)-b-1;
	for(int i=1;i<=m;i++){
		a[i].l=lower_bound(b+1,b+n+1,a[i].l)-b;
		a[i].r=lower_bound(b+1,b+n+1,a[i].r)-b;
	}
	for(int len=0;len<n;len++){
		for(int l=1;l+len<=n;l++){
			int r=l+len,pl=0;f[l][r]=inf;
			for(int i=1;i<=m;i++)
				if(a[i].l>=l&&a[i].r<=r&&a[i].d>a[pl].d)pl=i;
			if(pl==0){f[l][r]=0;continue;}
			for(int i=a[pl].l;i<=a[pl].r;i++)
				f[l][r]=min(f[l][r],f[l][i-1]+f[i+1][r]+a[pl].d);
		}
	}
	printf("%d\n",f[1][n]);return;
}

P5336

也是一道非常好的区间 DP 题目。会发现直接做非常不好做,所以还是按照上面的那种思路,从决策的角度来设计状态。考虑对于一个区间,肯定是未卜先知地选定一个值域区间 [x,y],把其它的数用最少的代价删掉,只保留其中的数。最后,用一个代价为 a+b(yx)2 的操作带走。然后开始思考如何做到保留特定的数,于是想到再加两维状态,用 fl,r,x,y 代表把 [l,r] 删的只剩下值在范围 [x,y] 中的最少代价,同时定义 gl,r 表示把区间删完的最少代价。fg 有简单的转移,而 f 的转移,直接枚举区间的断点,左右两半分别有两种决策,即同样剩下 [x,y] 和全部删完。复杂度 O(N5)。注意初始化。

signed main(){
	read(m);read(aa);read(bb);
	for(int i=1;i<=m;i++)read(a[i]),b[i]=a[i];
	sort(b+1,b+m+1);n=unique(b+1,b+m+1)-b-1;
	memset(f,0x3f,sizeof(f));memset(g,0x3f,sizeof(g));
	for(int i=1;i<=m;i++){
		a[i]=lower_bound(b+1,b+n+1,a[i])-b;f[i][i]=aa;
		for(int ll=1;ll<=a[i];ll++)for(int rr=a[i];rr<=n;rr++)g[i][i][ll][rr]=0;
	}
	for(int len=1;len<m;len++)for(int l=1;l+len<=m;l++){
		int r=l+len,nowData=inf;
		for(int sa=1;sa<=n;sa++)for(int sb=sa;sb<=n;sb++){
			int nowVal=inf;
			for(int i=l;i<r;i++)
				check(nowVal,min(f[l][i],g[l][i][sa][sb])+min(f[i+1][r],g[i+1][r][sa][sb]));
			g[l][r][sa][sb]=nowVal;
			check(f[l][r],nowVal+(b[sb]-b[sa])*(b[sb]-b[sa])*bb+aa);
		}
	}
	printf("%d\n",f[1][m]);return 0;
}
posted @   Feynn  阅读(29)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示