区间DP总结+例题

区间dp做题一般步骤:
首先,确定dp数组的意义。
1.枚举区间长度
2.枚举区间左端
3.确定区间右端,
4.枚举区间中的点更新dp

Eg1.Easy Game LightOJ - 1031

1031
You are playing a two player game. Initially there are n integer numbers in an array and player A and B get chance to take them alternatively. Each player can take one or more numbers from the left or right end of the array but cannot take from both ends at a time. He can take as many consecutive numbers as he wants during his time. The game ends when all numbers are taken from the array by the players. The point of each player is calculated by the summation of the numbers, which he has taken. Each player tries to achieve more points from other. If both players play optimally and player A starts the game then how much more point can player A get than player B?

Input
Input starts with an integer T (≤ 100), denoting the number of test cases.

Each case contains a blank line and an integer N (1 ≤ N ≤ 100) denoting the size of the array. The next line contains N space separated integers. You may assume that no number will contain more than 4 digits.

Output
For each test case, print the case number and the maximum difference that the first player obtained after playing this game optimally.

Sample Input
2

4

4 -10 -20 7
4

1 2 3 4

Sample Output
Case 1: 7

Case 2: 10

题意:有n个点排成一行,A和B可任意从两边取任意多个数,二者都采取最优策略,问A的和比B的和大多少。

和问题:处理前缀和。
选择:2个,左边取或者右边取。
dp[i][j]表示在i到j这段区间里先手最多比后手多多少分。

#include<cstdio>
#include<cstring>
using namespace std;
int a,sum[105];
int dp[105][105];
int main(){
	int t;
	scanf("%d",&t);
	int n;
	int cas=1;
	while(t--){
		scanf("%d",&n);
		memset(sum,0,sizeof(sum));
		memset(dp,0,sizeof(dp));
		for(int i=1;i<=n;i++){
			scanf("%d",&a);
			sum[i]=sum[i-1]+a;
			dp[i][i]=a;
		}
		for(int x=1;x<=n;x++){
			for(int i=1;i<=n;i++){
				int j=i+x;
				if(j>n)continue;
				dp[i][j]=sum[j]-sum[i-1];
				for(int k=i;k<j;k++){
					dp[i][j]=max(dp[i][j],max(sum[k]-sum[i-1]-dp[k+1][j],sum[j]-sum[k]-dp[i][k]));
				}//sum[k]-sum[i-1]表示先取的值,dp[k+1][j]是后手的最优策略
			}
		}
		printf("Case %d: ",cas++);
		cout<<dp[1][n]<<endl;
	}
	return 0;
} 

变形:Play Game HDU - 4597

hdu-4597
Alice and Bob are playing a game. There are two piles of cards. There are N cards in each pile, and each card has a score. They take turns to pick up the top or bottom card from either pile, and the score of the card will be added to his total score. Alice and Bob are both clever enough, and will pick up cards to get as many scores as possible. Do you know how many scores can Alice get if he picks up first?
Input
The first line contains an integer T (T≤100), indicating the number of cases.
Each case contains 3 lines. The first line is the N (N≤20). The second line contains N integer a i (1≤a i≤10000). The third line contains N integer b i (1≤b i≤10000).
Output
For each case, output an integer, indicating the most score Alice can get.
Sample Input
2

1
23
53

3
10 100 20
2 4 3
Sample Output
53
105

题意:A、B面前有两行数,可以从任意一边取任意多个数,问A的和比B的大多少。

记忆化搜索。先将所有数求和,针对四种选择两个区间进行四维dp。

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int a[25],b[25];
int dp[25][25][25][25];
int dfs(int i,int j,int l,int k,int sum){
	if(dp[i][j][l][k])return dp[i][j][l][k];
//	if(i>j&&l>k)return 0;
	int maxn=0;
	if(i<=j){
		maxn=max(maxn,sum-dfs(i+1,j,l,k,sum-a[i]));
		maxn=max(maxn,sum-dfs(i,j-1,l,k,sum-a[j]));
	}
	if(l<=k){
		maxn=max(maxn,sum-dfs(i,j,l+1,k,sum-b[l]));
		maxn=max(maxn,sum-dfs(i,j,l,k-1,sum-b[k]));
	}
	dp[i][j][l][k]=maxn;
	return maxn;
}
int main(){
	int t,n;
	cin>>t;
	while(t--){
		scanf("%d",&n);
		int sum=0;
		memset(dp,0,sizeof(dp));
		for(int i=1;i<=n;i++){
			scanf("%d",&a[i]);
			sum+=a[i];
		}
		for(int i=1;i<=n;i++){
			scanf("%d",&b[i]);
			sum+=b[i];
		}
		
		int s=dfs(1,n,1,n,sum);
		cout<<dp[1][n][1][n]<<endl;
	}
	return 0;
} 

Eg2.Combine String HDU - 5707

hdu-5707
Given three strings a, b and c, your mission is to check whether c is the combine string of a and b.
A string c is said to be the combine string of a and b if and only if c can be broken into two subsequences, when you read them as a string, one equals to a, and the other equals to b.
For example, adebcf'' is a combine string ofabc’’ and ``def’’.

Input
Input file contains several test cases (no more than 20). Process to the end of file.
Each test case contains three strings a, b and c (the length of each string is between 1 and 2000).
Output
For each test case, print Yes'', if c is a combine string of a and b, otherwise printNo’’.
Sample Input
abc
def
adebcf
abc
def
abecdf
Sample Output
Yes
No

题意:问string c是否由string a和string b组成。

dp[i][j]表示a的前i个和b的前j个能否组成c的前i+j个。
运用|运算符,当出现0时,即当前字母不能构成c时,后面全为0.
注意循环要多一层,不然(i+j)不能判断完。

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
string a,b,c;
int dp[2005][2005];
int main(){
	while(cin>>a>>b>>c){
		int len1=a.length();
		int len2=b.length();
		int len3=c.length();
		if(len1+len2!=len3){
			printf("No\n");
		}
		else{
			if(c[0]==a[0]||c[0]==b[0]){
			memset(dp,0,sizeof(dp));
			dp[0][0]=1;
			for(int i=0;i<=len1;i++){
				for(int j=0;j<=len2;j++){
					if(a[i]==c[i+j]&&i<len1){
						dp[i+1][j]|=dp[i][j];
					}
					if(b[j]==c[i+j]&&j<len2){
						dp[i][j+1]|=dp[i][j];
					}
				}
			}
			if(dp[len1][len2])printf("Yes\n");
			else printf("No\n");
		
		}
			else printf("No\n");
		}
	}
	return 0;
}

Eg3.String painter HDU - 2476

There are two strings A and B with equal length. Both strings are made up of lower case letters. Now you have a powerful string painter. With the help of the painter, you can change a segment of characters of a string to any other character you want. That is, after using the painter, the segment is made up of only one kind of character. Now your task is to change A to B using string painter. What’s the minimum number of operations?
Input
Input contains multiple cases. Each case consists of two lines:
The first line contains string A.
The second line contains string B.
The length of both strings will not be greater than 100.
Output
A single line contains one integer representing the answer.
Sample Input
zzzzzfzzzzz
abcdefedcba
abababababab
cdcdcdcdcdcd
Sample Output
6
7
题意:每次只能更改一个字符串的一段,且这一段只能更换为一个字母,问最少改多少次能将a改为b?

设有一个空串,dp[i][j]表示将i到j段转化为b的最少次数。
先假设每个都需要修改,dp[i][j]=dp[i+1][j]+1
当有相同的字母时取最小值。
再算a转化为b的最少次数。
当a和b不相同时,认为是b由空串转来的,考虑从哪个区间开始刷,枚举最优值。

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
string a,b;
int dp[105][105],sum[105];
int main(){
    while(cin>>a>>b){
        memset(dp,0,sizeof(dp));
        int len=b.length();

        for(int x=0;x<len;x++){
            for(int i=0;i<len-x;i++){
                int j=i+x;
                dp[i][j]=dp[i+1][j]+1;
                for(int k=i+1;k<=j;k++){
                    if(b[k]==b[i]){
                        dp[i][j]=min(dp[i][j],dp[i+1][k]+dp[k+1][j]);
                    }
                }
            }
        }
        memset(sum,0,sizeof(sum));
        for(int i=0;i<len;i++){
            sum[i]=dp[0][i];
        }
        for(int i=0;i<len;i++){
            if(a[i]==b[i]){
                if(i!=0){
                    sum[i]=sum[i-1];
                }
                else sum[i]=0;
            }
            else{
                for(int j=0;j<i;j++){
                    sum[i]=min(sum[i],sum[j]+dp[j+1][i]);
                }
            }
        }
        cout<<sum[len-1]<<endl;
    }
    return 0;
}

Eg4.Cutting Sticks UVA - 10003

10003

题意:有木棍长度为l,n个切点位置,每次花费为当前切割的木棍长度,求最小力气值。

经典区间dp,注意共有n+2个位置,包括a[0]=0和a[n+1]=l.

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int inf=1e9;
int l,n;
int a[55],sum[55],dp[55][55];
int main(){
	while(~scanf("%d",&l)){
		if(l==0)break;
		scanf("%d",&n);
		memset(sum,0,sizeof(sum));
		memset(dp,0,sizeof(dp));
		for(int i=1;i<=n;i++){
			scanf("%d",&a[i]);
		}
		a[0]=0;
		a[n+1]=l;
		for(int x=2;x<=n+1;x++){
			for(int i=0;i<=n-x+1;i++){
				int j=i+x;
				dp[i][j]=inf;
				for(int k=i;k<j;k++){
					dp[i][j]=min(dp[i][j],dp[i][k]+dp[k][j]+a[j]-a[i]);
					//cout<<dp[i][j]<<endl;
				}
			}
		}
		printf("The minimum cutting is %d.\n",dp[0][n+1]);
	}
	return 0;
}

Eg5.Palindrome subsequence HDU - 4632

链接
In mathematics, a subsequence is a sequence that can be derived from another sequence by deleting some elements without changing the order of the remaining elements. For example, the sequence <A, B, D> is a subsequence of <A, B, C, D, E, F>.
(http://en.wikipedia.org/wiki/Subsequence)

Given a string S, your task is to find out how many different subsequence of S is palindrome. Note that for any two subsequence X = <S x1, S x2, …, S xk> and Y = <S y1, S y2, …, S yk> , if there exist an integer i (1<=i<=k) such that xi != yi, the subsequence X and Y should be consider different even if S xi = S yi. Also two subsequences with different length should be considered different.
Input
The first line contains only one integer T (T<=50), which is the number of test cases. Each test case contains a string S, the length of S is not greater than 1000 and only contains lowercase letters.
Output
For each test case, output the case number first, then output the number of different subsequence of the given string, the answer should be module 10007.
Sample Input
4
a
aaaaa
goodafternooneveryone
welcometoooxxourproblems
Sample Output
Case 1: 1
Case 2: 31
Case 3: 421
Case 4: 960

题意:求回文字串的个数
dp+容斥定理
dp[i][j]=dp[i+1][j]+dp[i][j-1]-dp[i+1][j-1];
if(a[i]==a[j])dp[i][j]+=dp[i+1][j-1]+1//i和j也是一个字串
初始化自身是一个字串。
注意取模运算。

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int mod=1e4+7;
int dp[1005][1005];
string s;
int main(){
	int t;
	scanf("%d",&t);
	int cas=1;
	while(t--){
		cin>>s;
		int len=s.length();
		memset(dp,0,sizeof(dp));
		for(int i=0;i<len;i++){
			dp[i][i]=1;
		}
		for(int x=1;x<len;x++){
			for(int i=0;i<len;i++){
				int j=i+x;
				if(j>=len)continue;
				dp[i][j]=(dp[i+1][j]+dp[i][j-1]-dp[i+1][j-1]+mod)%mod;
				if(s[i]==s[j]){
					dp[i][j]=(dp[i+1][j-1]+1+dp[i][j])%mod; 
				}
			}
		}
		printf("Case %d: ",cas++);
		printf("%d\n",dp[0][len-1]%mod);
	}
	return 0;
}

Eg6.石子合并

链接
题目描述
在一个圆形操场的四周摆放N堆石子,现要将石子有次序地合并成一堆.规定每次只能选相邻的2堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分。

试设计出1个算法,计算出将N堆石子合并成1堆的最小得分和最大得分.

输入格式
数据的第1行试正整数N,1≤N≤100,表示有N堆石子.第2行有N个数,分别表示每堆石子的个数.

输出格式
输出共2行,第1行为最小得分,第2行为最大得分.

和问题:处理前缀和。
处理圆形,防止越界:a[i+n]=a[i]

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int inf=1e9;
int a[205],n;
int f1[205][205],f2[205][205],sum[205];
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);
		a[i+n]=a[i];
	}
	for(int i=1;i<=2*n;i++){
		sum[i]=sum[i-1]+a[i];
	}
//	memset(f1,0,sizeof(f1));
//	memset(f2,0,sizeof(f2));
	for(int x=2;x<=n;x++){
		for(int i=1;i<=2*n-x+1;i++){
			int j=i+x-1;
			f1[i][j]=0;
			f2[i][j]=inf;
			for(int k=i;k<j;k++){
			//	cout<<f1[i][k]<<"llP"<<f2[i][k]<<endl;
				f1[i][j]=max(f1[i][j],f1[i][k]+f1[k+1][j]+sum[j]-sum[i-1]);
				f2[i][j]=min(f2[i][j],f2[i][k]+f2[k+1][j]+sum[j]-sum[i-1]);
			///	cout<<f1[i][j]<<" "<<f2[i][j]<<endl; 
			}
		} 
	}
	int ans1=0,ans2=inf;
	for(int i=1;i<=n;i++){
		ans1=max(ans1,f1[i][i+n-1]);
		ans2=min(ans2,f2[i][i+n-1]);
	}
	cout<<ans2<<endl<<ans1<<endl;
	return 0;
} 
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值