【总结】区间DP(7.18)

一.概述

区间DP也属于线性DP中的一种,它以“区间长度”作为DP的“阶段”,使用两个坐标(区间的左、右端点)描述每个维度。在区间DP中,一个状态由若干个比它更小且包含于它的区间所代表的状态转移而来,因此区间DP的决策往往就是划分区间的方法。区间DP的初态一般就由长度为1的“元区间”构成。

把大区间划分成小区间,应该是问题的精髓

二.题目

http://222.180.160.110:1024/contest/629
http://222.180.160.110:1024/contest/597

删除字符串

题目描述
给出一个长度为n的字符串,每次可以删除一个字母相同的子串,问最少需要删多少次。 数据规模:n <= 500

输入格式
第1行:1个整数,表示字符串的长度

第2行:n个字符的字符串

输出格式
第1行:1个整数,表示答案

样例
样例输入
5
abaca
样例输出
3

解析:我们考虑割点
d p [ i ] [ j ] dp[i][j] dp[i][j]表示删去i到j的最小步数

  1. a [ i ] = = b [ j ] , 则 d p [ i ] [ j ] = d p [ i + 1 ] [ j − 1 ] a[i]==b[j],则dp[i][j]=dp[i+1][j-1] a[i]==b[j],dp[i][j]=dp[i+1][j1]
  2. a [ i ] ! = b [ j ] , 则 d p [ i ] [ j ] = m i n ( d p [ i + 1 ] [ j ] , d p [ i ] [ j − 1 ] ) + 1 a[i]!=b[j],则dp[i][j]=min(dp[i+1][j],dp[i][j-1])+1 a[i]!=b[j],dp[i][j]=min(dp[i+1][j],dp[i][j1])+1
    然而对于数据aabb,显然aa、bb分别为一组,而上述代码的答案是3,错误
  3. 考虑割点: d p [ i ] [ j ] = m i n ( d p [ i ] [ j ] , d p [ i ] [ k ] + d p [ k ] [ j ] − 1 ) , i + 1 < = k < = j − 1 dp[i][j]=min(dp[i][j],dp[i][k]+dp[k][j]-1),i+1<=k<=j-1 dp[i][j]=min(dp[i][j],dp[i][k]+dp[k][j]1),i+1<=k<=j1
    无论怎样我们都要考虑第3点,这样才是完整的。

法二:

dp[i][j]=dp[i+1][j]+1 for(int k=i+1;k<=j;k++) { if(a[i]==a[k]) dp[i][j]=min(dp[i][j],dp[i+1][k-1]+dp[k][j]); }

这里考虑左端点i,有两种情况:一是直接消i,二是i与其他点拼成一个点,但本质仍是割点。

小优化:初始序列中连续的相同的数可以看成一个点

代码一:

#include <cstdio> #include <cstring> #include <algorithm> using namespace std; const int maxn = 505; char c[maxn]; int n, cnt, a[maxn], dp[maxn][maxn]; int main() { scanf("%d", &n); scanf("%s", c); for (int i = 0; i < n; i++) { if (i == 0 || c[i] != c[i - 1]) a[++cnt] = c[i]; } for (int i = 1; i <= cnt; i++) dp[i][i] = 1; for (int len = 2; len <= cnt; len++) { for (int i = 1; i <= cnt - len + 1; i++) { int j = i + len - 1; if (a[i] == a[j]) dp[i][j] = dp[i + 1][j - 1] + 1; else dp[i][j] = min(dp[i + 1][j], dp[i][j - 1]) + 1; for (int k = i + 1; k <= j; k++) { dp[i][j] = min(dp[i][j], dp[i][k] + dp[k][j] - 1); } } } printf("%d", dp[1][cnt]); }

代码二:

#include <cstdio> #include <cstring> #include <algorithm> using namespace std; const int maxn = 505; char c[maxn]; int n, cnt, a[maxn], dp[maxn][maxn]; int main() { scanf("%d", &n); scanf("%s", c); for (int i = 0; i < n; i++) { if (i == 0 || c[i] != c[i - 1]) a[++cnt] = c[i]; } for (int i = 1; i <= cnt; i++) dp[i][i] = 1; for (int len = 2; len <= cnt; len++) { for (int i = 1; i <= cnt - len + 1; i++) { int j = i + len - 1; dp[i][j] = dp[i + 1][j] + 1; for (int k = i + 1; k <= j; k++) { if (a[i] == a[k]) dp[i][j] = min(dp[i][j], dp[i + 1][k - 1] + dp[k][j]); } } } printf("%d", dp[1][cnt]); }

[CQOI2007]涂色

解析:同上题,代码一样,只不过要转化一下:本题相当于从最终序列去还原成0的序列(全部删完),是逆向思维,请读者仔细思考

CF607B Zuma

思路:仍然是分类讨论,看i的消除方式

  1. 若a[i]==a[j],则dp[i][j]=dp[i+1][j-1],[i,j]一起消除
  2. 若a[i]==a[j],dp[i][j]=dp[i+1][j]+1,i单独消除
  3. 枚举k,若 a[i]==a[k],则[i,k]可能一起消除,dp[i][j] = min(dp[i][j], dp[i][k] + dp[k + 1][j]);
#include <cstdio> #include <cstring> #include <algorithm> #define ll long long using namespace std; const int maxn = 505; int n, a[maxn], dp[maxn][maxn]; int main() { scanf("%d", &n); for (int i = 1; i <= n; i++) scanf("%d", &a[i]); for (int i = 1; i <= n; i++) dp[i][i] = 1; for (int len = 2; len <= n; len++) { for (int i = 1; i <= n - len + 1; i++) { int j = i + len - 1; if (a[i] == a[j]) { if (i + 1 == j) dp[i][j] = 1; else dp[i][j] = dp[i + 1][j - 1]; } else dp[i][j] = dp[i + 1][j] + 1; for (int k = i; k <= j; k++) { if (a[i] == a[k]) dp[i][j] = min(dp[i][j], dp[i][k] + dp[k + 1][j]); } } } printf("%d", dp[1][n]); }

两只兔子

解析:枚举断点k,先求出 [i,j] 的最大回文长度,ans=max(ans,dp[1][i]+dp[i+1][n])

#include<cstdio> #include<cstring> #include<algorithm> #define ll long long using namespace std; const int maxn=2005; int n,a[maxn],dp[maxn][maxn]; void read(int &x) { char c=getchar(); while(c<'0'||c>'9') c=getchar(); while(c>='0'&&c<='9') {x=x*10+c-'0';c=getchar();} } int main() { while(scanf("%d",&n)&&n) { int ans=0; for(int i=1;i<=n;i++) { a[i]=0; read(a[i]); } for(int i=1;i<=n;i++) dp[i][i]=1; for(int len=2;len<=n;len++) { for(int i=1;i<=n-len+1;i++) { int j=i+len-1; if(a[i]==a[j]) dp[i][j]=dp[i+1][j-1]+2; else dp[i][j]=max(dp[i+1][j],dp[i][j-1]); } } for(int i=1;i<=n;i++) ans=max(ans,dp[1][i]+dp[i+1][n]); printf("%d\n",ans); } }

括号涂色

解析:分类讨论想到了,区间端点颜色也想到了,就是没有预处理左括号 l l l的对应位置 m a t c h [ l ] match_[l] match[l](可以证明是唯一的),然后综合能力不够,导致wa一片。。。

解决此类问题,首先要定义好状态,才能不重不漏,然后分类讨论,状态转移方程一定不能重复,若只有一种转移方式,可以预处理出来。然后就是记得取模。

#include <cstdio> #include <cstring> #include <algorithm> using namespace std; const int mul = 1e9 + 7; char s[800]; int lens; int match_[800]; int stack_[800]; long long f[800][800][3][3]; long long ans; void dp(int x, int y) { if (x + 1 == y) { f[x][y][0][1] = f[x][y][0][2] = 1LL; f[x][y][1][0] = f[x][y][2][0] = 1LL; return; } if (match_[x] == y) { dp(x + 1, y - 1); for (int i = 0; i <= 2; ++i) for (int j = 0; j <= 2; ++j) { if (i != 1) f[x][y][1][0] = (f[x][y][1][0] + f[x + 1][y - 1][i][j]) % mul; if (j != 1) f[x][y][0][1] = (f[x][y][0][1] + f[x + 1][y - 1][i][j]) % mul; if (i != 2) f[x][y][2][0] = (f[x][y][2][0] + f[x + 1][y - 1][i][j]) % mul; if (j != 2) f[x][y][0][2] = (f[x][y][0][2] + f[x + 1][y - 1][i][j]) % mul; } } else { int pair_ = match_[x]; dp(x, pair_); dp(pair_ + 1, y); for (int i = 0; i <= 2; ++i) for (int j = 0; j <= 2; ++j) for (int k = 0; k <= 2; ++k) for (int l = 0; l <= 2; ++l) if (!((k == 1 && l == 1) || (k == 2 && l == 2))) f[x][y][i][j] = (f[x][y][i][j] + (f[x][pair_][i][k] * f[pair_ + 1][y][l][j]) % mul) % mul; } } void get_match() { lens = strlen(s + 1); int top_ = 0; for (int i = 1; i <= lens; ++i) if (s[i] == '(') stack_[++top_] = i; else { match_[i] = top_; match_[stack_[top_]] = i; --top_; } } int main() { scanf("%s", s + 1); get_match(); memset(f, 0, sizeof(f)); dp(1, lens); ans = 0LL; for (int i = 0; i <= 2; ++i) for (int j = 0; j <= 2; ++j) ans = (ans + f[1][lens][i][j]) % mul; printf("%lld", ans); return 0; }

__EOF__

本文作者仰望星空的蚂蚁
本文链接https://www.cnblogs.com/cqbzly/p/17530417.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   仰望星空的蚂蚁  阅读(8)  评论(0编辑  收藏  举报  
编辑推荐:
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· DeepSeek 开源周回顾「GitHub 热点速览」
点击右上角即可分享
微信分享提示