算法总结—区间DP2

[USACO07OPEN] Cheapest Palindrome G

仍然是区间 DP,但时间复杂度不可能是 \(\mathcal{O}(m^3)\)

状态

\(dp_{i,j}\) 表示将区间 \([i,j]\) 变成回文串的最小代价。

答案

问题是将整个字符串改为回文串,所以答案是\(dp_{1,m}\)

状态转移方程

需要分类讨论。

  • \(s_i=s_j\),将区间 \([i,j]\) 改为回文串的代价就是将区间 \([i-1,j-1]\) 改为回文串的代价,即 \(dp_{i+1,j-1}\)

  • 删除左边的 \(dp_{i,j}=dp_{i+1,j}+y_{s_i}\)

  • 删除右边的 \(dp_{i,j}=dp_{i,j-1}+y_{s_j}\)

  • 增加左边的 \(dp_{i,j}=dp_{i+1,j}+x_{s_j}\)

  • 删除右边的 \(dp_{i,j}=dp_{i,j-1}+x_{s_i}\)

初始值

初始 \(dp_{i,i}=0\),但是转移中有 \(dp_{i+1,j-1}\),所以区间长度为 \(2\) 的也需要初始化。

时间复杂度是 \(\mathcal{O}(m^2)\)

代码

#include<bits/stdc++.h>
using namespace std;
int a[205];
int dp[2005][2005];
int main(){
	int n,m;
	cin>>n>>m;
	string s;
	cin>>s;
	s=" "+s;
	for(int i=1;i<=n;i++){
		char c;
		cin>>c;
		int x,y;
		cin>>x>>y;
		a[c]=min(x,y);
	}
	for(int i=1;i<=m;i++){
		for(int j=1;j<=m;j++){
			dp[i][j]=1e9;
		}
		dp[i][i]=0;
		if(s[i]==s[i+1]){
			dp[i][i+1]=0;
		}else{
			dp[i][i+1]=min(a[s[i]],a[s[i+1]]);
		}
	}
	for(int len=3;len<=m;len++){
		for(int i=1;i+len-1<=m;i++){
			int j=i+len-1;
			if(s[i]==s[j]){
				dp[i][j]=dp[i+1][j-1];
			}else{
				dp[i][j]=min(dp[i][j-1]+a[s[j]],dp[i+1][j]+a[s[i]]);
			}
		}
	}
	cout<<dp[1][m];
	return 0;
}

这种只能增加一个或者删除一个的时间复杂度一般是 \(\mathcal{O}(n^2)\)

[USACO06FEB] Treats for the Cows G/S

状态

\(dp_{i,j}\) 表示将区间 \([i,j]\) 的零食卖完所获得的最大价值。

答案

\(dp_{1,n}\)

状态转移方程

\(t=n-(j-i+1)+1\),表示一个零食是第几天卖出的。

需要分类讨论。

  • 卖出增左边的 \(dp_{i,j}=dp_{i+1,j}+a_i\times t\)

  • 卖出增右边的 \(dp_{i,j}=dp_{i,j-1}+a_i\times t\)

\(dp_{i,j}\) 就是这两种情况取最大值。

初始值

初始 \(dp_{i,i}=a_i\times n\)

时间复杂度 \(\mathcal{O}(n^2)\)

代码

#include<bits/stdc++.h>
using namespace std;
int a[2005];
int dp[2005][2005];
int main(){
	int n;
	cin>>n;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			dp[i][j]=-1e9;
		}
	}
	for(int i=1;i<=n;i++){
		cin>>a[i];
		dp[i][i]=a[i]*n;
	}
	for(int len=2;len<=n;len++){
		for(int i=1;i+len-1<=n;i++){
			int j=i+len-1;
			dp[i][j]=max(dp[i][j-1]+a[j]*(n-len+1),dp[i+1][j]+a[i]*(n-len+1));
		}
	}
	cout<<dp[1][n];
	return 0;
}

[HNOI2010] 合唱队

状态

\(dp_{i,j,0}\) 表示队列中最后进去的是 \(i\) 的方案数。

\(dp_{i,j,1}\) 表示队列中最后进去的是 \(j\) 的方案数。

答案

\(dp_{1,n,0}+dp_{1,n,1}\)

状态转移方程

  • \(h_i<h_{i+1}\)\(dp_{i,j,0}=dp_{i,j,0}+dp_{i+1,j,0}\)

  • \(h_i<h_j\)\(dp_{i,j,0}=dp_{i,j,0}+dp_{i+1,j,1}\)

  • \(h_j>h_{j-1}\)\(dp_{i,j,1}=dp_{i,j,1}+dp_{i,j-1,1}\)

  • \(h_j>h_i\)\(dp_{i,j,1}=dp_{i,j,1}+dp_{i,j-1,0}\)

初始值

要么 \(dp_{i,i,0}=1\),要么 \(dp_{i,i,1}=1\),否则在转移过程中会重复。

代码

#include<bits/stdc++.h>
using namespace std;
const int mod=19650827;
int a[1005];
int dp[1005][1005][2];
int main(){
	int n;
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		dp[i][i][0]=1;
	}
	for(int len=2;len<=n;len++){
		for(int i=1;i+len-1<=n;i++){
			int j=i+len-1;  
			if(a[i]<a[i+1]){
				dp[i][j][0]=(dp[i][j][0]+dp[i+1][j][0])%mod;
			}
			if(a[i]<a[j]){
				dp[i][j][0]=(dp[i][j][0]+dp[i+1][j][1])%mod;
			}
			if(a[j]>a[j-1]){
				dp[i][j][1]=(dp[i][j][1]+dp[i][j-1][1])%mod;
			}
			if(a[j]>a[i]){
				dp[i][j][1]=(dp[i][j][1]+dp[i][j-1][0])%mod;
			}
		}
	}
	cout<<(dp[1][n][0]+dp[1][n][1])%mod;
	return 0;
}
posted @ 2025-04-21 14:42  LRRabcd  阅读(27)  评论(0)    收藏  举报