区间dp

区间 \(dp\) 通常解决的问题顺序有着很重要的作用,也就是说“前 \(i\) 个”的状态并不能很好地刻画出完整的状态
状态一般设计为 \(f[l][r]\),表示 \(dp\)\([l,r]\) 的区间的花费
后面经常补充状态,比如 \([0/1]\)\([k]\) 来表示上一次取的是左/右端点,左右端点的状态,或者区间后面的一段情况,或者区间内数具有的性质


从中间转移

即每次扩展的是一个区间,用于解决合并类的问题

最典型的有石子合并
再补充一些题目:


P3146 [USACO16OPEN]248 G

发现同样是一个合并类问题,那么 \(f[i][j]=f[i][k]+1 (f[i][k]==f[k][j] )\)


CF149D Coloring Brackets

\(f[l][r][0/1/2][0/1/2]\) 表示左右端点颜色分别为 \(0/1/2\) 的方案数
可以看左右端点是否是匹配括号,是则 \(dp\) \([l+1,r-1]\),否则 \(dp\) \([l,k]\)\([k+1,r]\)
这道题的一个启发点是对于这种括号匹配的问题,往往写成递归版的记忆化搜索会更好实现


凸多边形的划分

\(f[l][r]=f[l+1][k-1]+f[k+1][r-1]+a[l]* a[k]* a[r]\),表示 \(l,k,r\) 三个点形成了三角形并产生贡献


P7914 [CSP-S 2021] 括号序列

并不想讲这个题,来补充一个 道听途说未曾实现不保证正确 的小 \(trick\)
这种括号匹配经常有算重的问题,那么对于每一个 \(l\)\(r\) 从小到大枚举据说即可解决这个问题(理论上分析比较有道理)


Replace

\(Cyber_Tree\) 教了一晚上

关键条件是转化关系是 \(1\) 对多的,那么设 \(f[l][r][c]\) 表示 \(T\) 串区间 \([l,r]\) 转化为字母 \(c\) 的最优方案
那么对于多步转移,由于越来越少(\(1\)\(1\) 需要特判),不会转移成环


Range Argmax

实际上就是枚举局部最大值的位置,可以发现这个是不能计数的,应该放在状态里
形式化地,为经过 \(i\) 位置的所有区间最大值均在 \(i\)
很显然有多个,那么钦定为第一个
那么转移从 \([l,k)\)\((k,r]\) 转移
其中左边一个区间可以发现需要迎合 \(k\) 是最大值这个限制
那么需要保证 \(k'\)\(k\) 管辖区间无交,这个可以对每个 \((l,r,k)\) 预处理出来
注意右边是没有这个限制的,因为定义了是最小的一个,而这个最大值并不唯一
发现左边的转移是一个区间,那么维护一个前缀和即可


从两侧转移

POJ 3280 Cheapest Palindrome

题意:给你长度为m的字符串,其中有n种字符,每种字符都有两个值,分别是插入这个字符的代价,删除这个字符的代价,让你求将原先给出的那串字符变成一个回文串的最小代价。

这道题的思想还是很妙的
分情况讨论:

  • 如果 \(l,r\) 相等,从 \(f[l+1,r-1]\) 转移
  • 在前面插入 \(r\) 或后面删除 \(r\),从 \(f[l,r-1]\) 转移
  • 在后面插入 \(l\) 或前面删除 \(l\),从 \(f[l+1,r]\) 转移

队列

很妙的题,当时不以为然,AT 又出来发现不会
分两个数组 \(f,g\) 分别维护小明和小华取完后的状态
那么 \(f[l,r]=max(f[l+1,r]+a[l],f[l,r-1]+a[r])\)
\(g\) 的转移同理


P1220 关路灯

古老的题了,设 \(f[l][r][0/1]\) 表示区间 \([l,r]\),最后一次老王在左/右端点的最优值


维护后缀信息

P2135 方块消除

\(f[l][r][s]\) 表示区间 \([l,r]\),后面紧接着有 \(s\) 个与 \(r\) 相同的方块
这样的状态使得一种转移迎刃而解:即选取中间一个点 \(k\),其中 \(k\)\(r\) 颜色相同,砍掉 \([k+1,r-1]\) 的部分,让 \(r\) 以及后面的 \(s\) 个与 \(k\) 拼上
那么转移有两个:\(f[l][r][s]=f[l][r-1][0]+(s+a[r])^2\)
\(f[l][r][s]=f[l][k][a[r]+s]+f[k+1][r-1][0]\)

  • 这启示我们把转移不了的东西放进状态里
代码
#include<bits/stdc++.h>
using namespace std;
int read(){
	int x=0,f=1;
	char ch=getchar();
	while(!isdigit(ch)){
		if(ch=='-')f=-1;
		ch=getchar();
	}
	while(isdigit(ch)){
		x=x*10+ch-48;
		ch=getchar();
	}
	return x*f;
}
const int maxn=205;
const int inf=0x3f3f3f3f;
int n,col[maxn],a[maxn],f[maxn][maxn][maxn*2],sum[maxn];
int main(){
	n=read();
	for(int i=1;i<=n;i++)col[i]=read();
	for(int i=1;i<=n;i++)a[i]=read(),sum[i]=sum[i-1]+a[i];
//	for(int i=1;i<=n;i++)f[i][i][0]=a[i]*a[i];
	for(int len=0;len<=n-1;len++){
		for(int l=1;l<=n-len;l++){
			int r=l+len;
			for(int s=0;s<=sum[n]-sum[r-1];s++){
				f[l][r][s]=f[l][r-1][0]+(s+a[r])*(s+a[r]);
				for(int k=l;k<=r-1;k++){
					if(col[k]==col[r])
						f[l][r][s]=max(f[l][r][s],f[l][k][a[r]+s]+f[k+1][r-1][0]);
				}
			}
		}
	}
	cout<<f[1][n][0];
	return 0;
}

双倍经验


P5336 [THUSC2016]成绩单

发现既然一次分发只和最值有关,那么只记录区间最值即可
\(g[l][r][mn][mx]\) 表示 \([l,r]\) 中最值分别为 \(mn\)\(mx\) 的最优情况
\(f[l][r]\) 表示区间 \([l,r]\) 的最优解
那么 \(f[l][r]=min{g[l][r][a][b]+B*(b-a)^2+A}\)
考虑枚举断点 \(k\) 来转移 \(g\)
\(g[l][r][a][b]=min(f[k+1][r]+g[l][k][a][b])\)
\(g[l][r][a][b]->g[l][r+1][min(a,w_{r+1})][max(b,w_{r+1})]\)
于是离散化后用 \(n^5\) 的优秀复杂度完成了此题

代码
#include<bits/stdc++.h>
using namespace std;
int read(){
	int x=0,f=1;
	char ch=getchar();
	while(!isdigit(ch)){
		if(ch=='-')f=-1;
		ch=getchar();
	}
	while(isdigit(ch)){
		x=x*10+ch-48;
		ch=getchar();
	}
	return x*f;
}
const int maxn=55;
int n,m,aa,b,a[maxn],lsh[maxn],tot,f[maxn][maxn],g[maxn][maxn][maxn][maxn];
void placemin(int &a,int b){
	if(a>b)a=b;
	return ;
}
int main(){
	n=read(),aa=read(),b=read();
	for(int i=1;i<=n;i++)lsh[i]=a[i]=read();
	sort(lsh+1,lsh+n+1);
	tot=unique(lsh+1,lsh+n+1)-lsh-1;
	for(int i=1;i<=n;i++)a[i]=lower_bound(lsh+1,lsh+tot+1,a[i])-lsh;
	memset(g,0x3f,sizeof g),memset(f,0x3f,sizeof f);
	for(int i=1;i<=n;i++)g[i][i][a[i]][a[i]]=0;
	for(int len=0;len<=n-1;len++){
		for(int l=1;l<=n-len;l++){
			int r=l+len;
			for(int mn=1;mn<=tot;mn++){
				for(int mx=mn;mx<=tot;mx++){
					for(int k=l;k<r;k++){
						placemin(g[l][r][mn][mx],g[l][k][mn][mx]+f[k+1][r]);
					}
					placemin(g[l][r+1][min(mn,a[r+1])][max(mx,a[r+1])],g[l][r][mn][mx]);
					placemin(f[l][r],g[l][r][mn][mx]+aa+b*(lsh[mx]-lsh[mn])*(lsh[mx]-lsh[mn]));
				}
			}
		}
	}
	cout<<f[1][n];
	return 0;
}

P1436 棋盘分割

这是区间 \(dp\) 放到二维平面上的例子
\(f[l][r][i][j][k]\) 表示顶点为 \((l,r)\)\((i,j)\) 的矩形分割了 \(k\) 次的最小值
每次只扩展横纵坐标之一

代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=16;
int n,a[maxn][maxn],sum[maxn][maxn],f[maxn][maxn][maxn][maxn][maxn];
void tomin(int &a,int b){
	if(a>b)a=b;
	return ;
}
int main(){
	cin>>n;
	for(int i=1;i<=8;i++){
		for(int j=1;j<=8;j++){
			cin>>a[i][j];
			sum[i][j]=sum[i-1][j]+sum[i][j-1]+a[i][j]-sum[i-1][j-1];
		}
	}
	memset(f,0x3f,sizeof f);
	for(int i=1;i<=8;i++){
		for(int j=1;j<=8;j++){
			for(int l=i;l<=8;l++){
				for(int r=j;r<=8;r++){
					int w=sum[l][r]-sum[i-1][r]-sum[l][j-1]+sum[i-1][j-1];
					f[i][j][l][r][0]=w*w;
				}
			}
		}
	}
	for(int k=1;k<=n;k++){
		for(int i=1;i<=8;i++){
			for(int j=1;j<=8;j++){
				for(int l=1;l<=8;l++){
					for(int r=1;r<=8;r++){
						for(int a=i;a<l;a++){
							tomin(f[i][j][l][r][k],min(f[i][j][a][r][0]+f[a+1][j][l][r][k-1],f[i][j][a][r][k-1]+f[a+1][j][l][r][0]));
						}
						for(int b=j;b<r;b++){
							tomin(f[i][j][l][r][k],min(f[i][j][l][b][0]+f[i][b+1][l][r][k-1],f[i][j][l][b][k-1]+f[i][b+1][l][r][0]));
						}
					}
				}
			}
		}
	}
	cout<<f[1][1][8][8][n-1];
	return 0;
}

P1864 [NOI2009] 二叉查找树

根据题目的限制,不能更改数据值,也就是说这棵 \(BST\) 的中序遍历是一定了,要做的是通过改变权值来旋转
\(f[l][r][s]\) 表示中序 \([l,r]\) 的节点,值 \(\ge s\) 的最小值
那么枚举一个树根 \(k\),转移分情况讨论:
如果 \(k\) 的值大于等于 \(s\),那么 \(f[l][r][s]=f[l][k-1][a[k].val]+f[k+1][j][a[k].val]+sum(i,j)\)
否则,\(f[l][r][s]=f[l][k-1][s]+f[k+1][r][s]+sum(i,j)+K\)

代码
#include<bits/stdc++.h>
using namespace std;
int read(){
	int x=0,f=1;
	char ch=getchar();
	while(!isdigit(ch)){
		if(ch=='-')f=-1;
		ch=getchar();
	}
	while(isdigit(ch)){
		x=x*10+ch-48;
		ch=getchar();
	}
	return x*f;
}
const int maxn=75;
const int inf=1e9;
int n,m,f[maxn][maxn][maxn],sum[maxn],lsh[maxn];
struct Node{
	int val,val1,cost;
}a[maxn];
bool cmp(Node a,Node b){
	return a.val<b.val;
}
int main(){
	n=read();m=read();
	for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)for(int k=1;k<=n;k++)if(j!=i-1)f[i][j][k]=inf;
	for(int i=1;i<=n;i++)a[i].val=read();
	for(int i=1;i<=n;i++)lsh[i]=a[i].val1=read();
	for(int i=1;i<=n;i++)a[i].cost=read();
	sort(a+1,a+n+1,cmp);
	sort(lsh+1,lsh+n+1);
	for(int i=1;i<=n;i++)a[i].val1=lower_bound(lsh+1,lsh+n+1,a[i].val1)-lsh;
	for(int i=1;i<=n;i++)sum[i]=sum[i-1]+a[i].cost;
//	for(int i=1;i<=n;i++){
//		for(int j=a[i].val1;j<=n;j++)f[i][i][j]=a[i].cost;
//		for(int j=1;j<a[i].val1;j++)f[i][i][j]=a[i].cost+m;
//	}
	for(int len=0;len<=n-1;len++){
		for(int l=1;l<=n-len;l++){
			int r=l+len;
			for(int s=1;s<=n;s++){
				for(int k=l;k<=r;k++){
					if(a[k].val1>=s)f[l][r][s]=min(f[l][r][s],f[l][k-1][a[k].val1]+f[k+1][r][a[k].val1]+sum[r]-sum[l-1]);
					f[l][r][s]=min(f[l][r][s],f[l][k-1][s]+f[k+1][r][s]+sum[r]-sum[l-1]+m);
				}
			}
		}
	}
	cout<<f[1][n][1];
	return 0;
}

B. ZYB玩字符串

题意:每次可以删除串中的一个 \(S\),最少剩下多长

其意义在于这种消除的模式并不能贪心,其具有区间合并性
\(dp\) 状态相当于也是维护了后缀信息
详见 这里

代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=205;
int t,n,lenans,nxt[maxn],cnt,sum[30],sum1[30];
bool f[maxn][maxn];
char a[maxn],b[maxn],c[maxn],ans[maxn];
void solve(int l,int r){
	int len=r-l+1;
	for(int i=l;i<=r;i++)b[i-l+1]=a[i];
	memset(f,0,sizeof f);
	for(int i=1;i<=n;i++){
		f[i][i-1]=true;
	}
	for(int len1=0;len1<=n-1;len1++){
		for(int l=1;l<=n-len1;l++){
			int r=l+len1;
			f[l][r]|=f[l][r-1]&(a[r]==b[len1%len+1]);
			for(int k=1;k*len<=len1;k++){
				f[l][r]|=f[l][r-k*len]&f[r-k*len+1][r];
				if(f[l][r])break;
			}
//			cout<<l<<" "<<r<<" "<<f[l][r]<<endl;
		}
	}
	if(f[1][n]){
		lenans=len;
		for(int i=1;i<=len;i++)ans[i]=b[i];
	}
	return ;
}
bool cmp(int len,int l,int r){
	if(!len)return true;
	for(int i=l;i<=r;i++){
		if(a[i]!=ans[i-l+1])return a[i]<ans[i-l+1];
	}
	return false;
}
bool check(int l,int r){
	memset(sum1,0,sizeof sum1);
	int base=n/(r-l+1);
	for(int i=l;i<=r;i++){
		sum1[a[i]-'a']++;
	}
//	cout<<"hhh"<<endl;
	for(int i=0;i<26;i++){
		if(sum1[i]*base!=sum[i])return false;
	}
	return true;
}
int main(){
	freopen("string.in","r",stdin);
	freopen("string.out","w",stdout);
	cin>>t;
	while(t--){
		memset(sum,0,sizeof sum);
		scanf("%s",a+1);
		n=strlen(a+1);lenans=0;
		for(int i=1;i<=n;i++){
			sum[a[i]-'a']++;
		}
		for(int len=0;len<=n-1;len++){
//			cout<<n<<" "<<len+1<<endl;
			if(n%(len+1))continue;
//			cout<"hhh";
			for(int l=1;l<=n-len;l++){
				int r=l+len;
				if(cmp(lenans,l,r)){
//					cout<<"hhh";
					if(check(l,r))solve(l,r);
				}
			}
			if(lenans)break;
		}
		for(int i=1;i<=lenans;i++){
			printf("%c",ans[i]);
		}
		puts("");
	}
	return 0;
}

  • 资料

日报

  • 咕咕咕

区间 \(dp\) 与四边形不等式连接紧密,继续往后咕

posted @ 2022-08-05 22:15  y_cx  阅读(58)  评论(0编辑  收藏  举报