心得
最近的题接触了许多区间dp题,感觉有必要总结一下做法。
区间dp大致上就是dp某一段区间,通过不断递推最后扩展至整个区间从而求出答案,涉及的题型比较多,但基本都是通过枚举起始点,终点与中间一个进行某种操作的点进行转移,以下是一些做到的例题(持续更新中)。
Easy Game - lightOj 1031
题意
AB两人轮流从一序列的左右两端取数,A先手,一次可以取多个,但只能从一端取,不能不取,问A取的数之总和最多比B大多少
题解
比较容易想到的区间dp,令dp[i][j]存放在i到j的区间内A取的数总和最多能比B的总和大的值,预处理出pre数组表示前i个数的总和,之后我们枚举区间起点i,终点j与一个中间点k,对于当前轮到的人取i-k区间的数,k-j的数取最大值并比较dp[i][j]本身,注意对于每一段dp[i][j]的初值是这些数的总和
#include<bits/stdc++.h>
using namespace std;
typedef long long int LL;
const int MAXN=110;
LL pre[MAXN],dp[MAXN][MAXN];
int main(){
int T,cnt=0;
scanf("%d",&T);
while(T--){
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++){
int num;
scanf("%d",&num);
pre[i]=pre[i-1]+num;
dp[i][i]=num;
}
for(int len=1;len<=n;len++){
for(int i=1;i<=n-len;i++){
int j=i+len;
dp[i][j]=pre[j]-pre[i-1];
for(int k=i;k<j;k++){
dp[i][j]=max(dp[i][j],max((pre[k]-pre[i-1])-dp[k+1][j],(pre[j]-pre[k])-dp[i][k]));
}
}
}
printf("Case %d: %lld\n",++cnt,dp[1][n]);
}
}
Generating Palindromes - lightOJ 1033
题意
给出一个字符串,问最小所需的字符数量使得插入这些字符后,该字符串成为回文串
题解
回文串+区间dp,dp[i][j]表示使给定字符串的i-j段成为回文串的最小插入字符数量,
在记忆化搜索中有三类情况,一种是i>=j,i>j的段是无意义的,而i=j的段本身肯定是回文,不需插入字符,都是0,之后分c[i]=c[j]和c[i]!=c[j]两类情况,如果c[i]=c[j],继续往这段字符串的中间搜索,即dfs(i+1,j-1),否则我们尝试在最左端和最右端插入字符并取最小值,即min(dfs(i+1,j)+1,dfs(i,j-1)+1)
#include<bits/stdc++.h>
using namespace std;
const int MAXN=110;
char c[MAXN];
int dp[MAXN][MAXN];
bool is_palindrome(int i,int j){
for(int pos=i;pos<=(i+j)/2;pos++){
if(c[pos]!=c[j-(pos-i)]) return false;
}
return true;
}
void init(){
memset(dp,-1,sizeof(dp));
}
int dfs(int i,int j){
int ans=0;
if(dp[i][j]!=-1) return dp[i][j];
if(i>=j) return dp[i][j]=0;
else if(c[i]==c[j]){
return dp[i][j]=dfs(i+1,j-1);
}
else{
return dp[i][j]=min(dfs(i+1,j)+1,dfs(i,j-1)+1);
}
}
int main(){
int T,cnt=0;
scanf("%d",&T);
while(T--){
init();
scanf("%s",c);
int len=strlen(c)-1;
if(is_palindrome(0,len)==1){
printf("Case %d: 0\n",++cnt);
continue;
}
dfs(0,len);
printf("Case %d: %d\n",++cnt,dp[0][len]);
}
}
Palindrome Partitioning - lightOJ 1044
题意
T组数据,每组数据输入一个字符串,问将其分为回文子串的最小数量
题解
dp[i]表示字符串中区间c[0]-c[i]分为回文子串的最小数量,显然dp[i]的初值赋为i+1,之后枚举区间的结束端点i和中间点j,如果区间j-i恰好为回文串,那么可以将这一段分为一个子串,也可以暂时不分,更新dp[i]=min(dp[i],dp[j]+1),最后输出dp[len]
#include<bits/stdc++.h>
using namespace std;
const int MAXN=1010;
char c[MAXN];
int dp[MAXN];
bool is_palindrome(int i,int j){
for(int pos=i;pos<=(i+j)/2;pos++){
if(c[pos]!=c[j-(pos-i)]) return false;
}
return true;
}
int main(){
int T,cnt=0;
scanf("%d",&T);
while(T--){
scanf("%s",c);
int len=strlen(c)-1;
if(is_palindrome(0,len)==1){
printf("Case %d: 1\n",++cnt);
continue;
}
for(int i=0;i<=len;i++){
dp[i]=i+1;
}
for(int i=0;i<=len;i++){
for(int j=0;j<=i;j++){
if(is_palindrome(j,i)==1){
dp[i]=min(dp[i],1+dp[j-1]);
}
}
}
printf("Case %d: %d\n",++cnt,dp[len]);
}
}
The Specials Menu - lightOJ 1025
题意
T组数据,每组数据给出一个字符串,问删去字符使其成为回文串的方案数
题解
和Generating Palindromes一题很相似,dp[i][j]表示i-j区间内的删成回文的方案数,如果字符c[i]=c[j],那么dp[i][j]=dp[i+1][j]+dp[i][j-1]+1,否则dp[i][j]=dp[i+1][j]+dp[i][j-1]-dp[i+1][j-1]
#include<cstring>
#include<cstdio>
using namespace std;
typedef long long int LL;
const int MAXN=110;
char c[MAXN];
LL dp[MAXN][MAXN];
LL dfs(int i,int j){
if(dp[i][j]!=-1) return dp[i][j];
if(i>j) return dp[i][j]=0;
if(i==j) return dp[i][j]=1;
LL ans=dfs(i+1,j)+dfs(i,j-1);
if(c[i]==c[j]) ans++;
else ans-=dfs(i+1,j-1);
return dp[i][j]=ans;
}
int main(){
int T,cnt=0;
scanf("%d",&T);
while(T--){
scanf("%s",c+1);
int len=strlen(c+1);
memset(dp,-1,sizeof(dp));
printf("Case %d: %lld\n",++cnt,dfs(1,len));
}
return 0;
}