Codeforces 1312E. Array Shrinking(区间DP 栈)

linkkk

题意:

给出一个长度为 n n n的序列,对于相同相邻的两个数 x x x可以替换为一个数 x + 1 x+1 x+1,问该序列的最短长度是多少。
n < = 500 n<=500 n<=500

思路:

看到数据范围不难想到是个区间dp
d p [ l ] [ r ] dp[l][r] dp[l][r]表示区间 [ l , r ] [l,r] [l,r]能够合并成的最短序列长度。
那么 d p [ l ] [ r ] = m i n ( d p [ l ] [ k ] + d p [ k + 1 ] [ r ] ) dp[l][r]=min(dp[l][k]+dp[k+1][r]) dp[l][r]=min(dp[l][k]+dp[k+1][r])
什么时候 [ l , k ] [l,k] [l,k] [ k + 1 , r ] [k+1,r] [k+1,r]也可以合并呢?当两个区间的最短长度为 1 1 1并且剩下的数相同时。
b [ l ] [ r ] b[l][r] b[l][r]表示区间 [ l , r ] [l,r] [l,r]合并后剩余的数
就可以转移了。
时间复杂度 O ( n 3 ) O(n^3) O(n3)

代码:

// Problem: E. Array Shrinking
// Contest: Codeforces - Educational Codeforces Round 83 (Rated for Div. 2)
// URL: https://codeforces.com/problemset/problem/1312/E
// Memory Limit: 256 MB
// Time Limit: 2000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

#include<iostream>
#include<cstdio> 
#include<string>
#include<ctime>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<stack>
#include<queue>
#include<map>
#include<set>
#include<sstream>
using namespace std;

typedef long long ll;

const int maxn=2e5+100;

int a[510],b[510][510],dp[510][510];

int main() {
	int n;cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i];
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++){
			dp[i][j]=j-i+1;
			if(i==j) b[i][i]=a[i];
		}
	
	for(int len=1;len<=n;len++){
		for(int l=1;l+len-1<=n;l++){
			int r=l+len-1;
			for(int k=l;k<=r-1;k++){
				dp[l][r]=min(dp[l][r],dp[l][k]+dp[k+1][r]);
				if(dp[l][k]==1&&dp[k+1][r]==1&&b[l][k]==b[k+1][r]){
					dp[l][r]=1;
					b[l][r]=b[l][k]+1;
				}
			}
		}
	}
		
	cout<<dp[1][n]<<endl;
	
	
	

	return 0;
}

思路:

考虑用另一种表示状态: d p [ i ] dp[i] dp[i]表示 [ 1 , i ] [1,i] [1,i]合并后的最短长度,那么最后的答案便是 [ 1 , n ] [1,n] [1,n]
转移的话,如果说 [ k , i ] [k,i] [k,i]可以合并为一个数,那么显然 d p [ i ] = d p [ k − 1 ] + 1 dp[i]=dp[k-1]+1 dp[i]=dp[k1]+1
问题就转化为了如何快速判断区间 [ k , i ] [k,i] [k,i]能否合并为一个数。
延伸出两种思路:

  • 借助括号匹配的思想,运用栈来判断。具体做法为:枚举右端点,对于每一个右端点,维护一个栈,然后倒着向前枚举,如果当前元素 x x x和栈顶元素相同,弹出栈顶,说明两者合并变为了 x + 1 x+1 x+1。最后如果说栈的大小为 1 1 1说明该段区间可以合并为一个数。
  • 用区间 d p dp dp来解, f [ l ] [ r ] f[l][r] f[l][r]表示区间 [ l , r ] [l,r] [l,r]合并后的数为多少,如果为 0 0 0则说明无法合并为一个数。枚举中间点,如果说 f [ l ] [ k ] = = f [ k + 1 ] [ r ] f[l][k]==f[k+1][r] f[l][k]==f[k+1][r] f [ l ] [ r ] = f [ l ] [ k ] + 1 f[l][r]=f[l][k]+1 f[l][r]=f[l][k]+1.

前者的时间复杂度是 O ( n 2 ) O(n^2) O(n2),后者为 O ( n 3 ) O(n^3) O(n3)

代码:

// Problem: E. Array Shrinking
// Contest: Codeforces - Educational Codeforces Round 83 (Rated for Div. 2)
// URL: https://codeforces.com/problemset/problem/1312/E
// Memory Limit: 256 MB
// Time Limit: 2000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

#include<iostream>
#include<cstdio> 
#include<string>
#include<ctime>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<stack>
#include<queue>
#include<map>
#include<set>
#include<sstream>
using namespace std;

typedef long long ll;

const int maxn=2e5+100;

int a[510],b[510][510],dp[510];

int main() {
	int n;cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i];
	for(int r=1;r<=n;r++){
		dp[r]=dp[r-1]+1;
		stack<int>st;
		st.push(a[r]);
		for(int l=r-1;l;l--){
			int now=a[l];
			while(1){
				if(st.empty()){
					st.push(now);break;
				}
				if(now==st.top()) st.pop(),now++;
				else{
					st.push(now);break;
				}
			}
			if(st.size()==1) dp[r]=min(dp[r],dp[l-1]+1);
		}
	}
	cout<<dp[n]<<endl;
	
	

	return 0;
}

区间dp

// Problem: E. Array Shrinking
// Contest: Codeforces - Educational Codeforces Round 83 (Rated for Div. 2)
// URL: https://codeforces.com/problemset/problem/1312/E
// Memory Limit: 256 MB
// Time Limit: 2000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

#include<iostream>
#include<cstdio> 
#include<string>
#include<ctime>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<stack>
#include<queue>
#include<map>
#include<set>
#include<sstream>
using namespace std;

typedef long long ll;

const int maxn=2e5+100;

int a[510],f[510][510],dp[510];

int main() {
	int n;cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i],f[i][i]=a[i];
	for(int len=1;len<=n;len++)
		for(int l=1;l+len-1<=n;l++){
			int r=l+len-1;
			for(int k=l;k<=r;k++)
				if(f[l][k]!=0&&f[l][k]==f[k+1][r])
					f[l][r]=f[l][k]+1;
		}
	memset(dp,0x3f,sizeof dp);
	dp[0]=0;
	for(int r=1;r<=n;r++)
		for(int l=1;l<=r;l++)
			if(f[l][r]!=0) dp[r]=min(dp[r],dp[l-1]+1);
	cout<<dp[n];
	return 0;
}

总结:

d p dp dp状态不同决定了时间复杂度的不同,对于某个 d p dp dp状态来说,又会有好几种解法。
参考:
博客1
博客2

区间DP是一种动态规划的方法,用于解决区间范围内的问题。在Codeforces竞赛中,区间DP经常被用于解决一些复杂的字符串或序列相关的问题。 在区间DP中,dp[i][j]表示第一个序列前i个元素和第二个序列前j个元素的最优解。具体的转移方程会根据具体的问题而变化,但是通常会涉及到比较两个序列的元素是否相等,然后根据不同的情况进行状态转移。 对于区间长度为1的情况,可以先进行初始化,然后再通过枚举区间长度和区间左端点,计算出dp[i][j]的值。 以下是一个示例代码,展示了如何使用区间DP来解决一个字符串匹配的问题: #include <cstdio> #include <cstring> #include <string> #include <iostream> #include <algorithm> using namespace std; const int maxn=510; const int inf=0x3f3f3f3f; int n,dp[maxn][maxn]; char s[maxn]; int main() { scanf("%d", &n); scanf("%s", s + 1); for(int i = 1; i <= n; i++) dp[i][i] = 1; for(int i = 1; i <= n; i++) { if(s[i] == s[i - 1]) dp[i][i - 1] = 1; else dp[i][i - 1] = 2; } for(int len = 3; len <= n; len++) { int r; for(int l = 1; l + len - 1 <= n; l++) { r = l + len - 1; dp[l][r] = inf; if(s[l] == s[r]) dp[l][r] = min(dp[l + 1][r], dp[l][r - 1]); else { for(int k = l; k <= r; k++) { dp[l][r] = min(dp[l][r], dp[l][k] + dp[k + 1][r]); } } } } printf("%d\n", dp[n]); return 0; } 希望这个例子能帮助你理解区间DP的基本思想和应用方法。如果你还有其他问题,请随时提问。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

豆沙睡不醒

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值