【模板题】动态规划 石子合并、括号匹配、加分二叉树——区间dp问题及其整理

原题 [NOIP2003]加分二叉树

题目大意:输入一棵树的中序遍历,定义一棵子树的得分为其左子树的加分×右子树的加分+根的分数。求最大得分及先序遍历

注意:
1、初始化 r[i][i]=i,便于输出
2、 初始化dp[i][i-1]=dp[i+1][i]=1。因为在区间中选取一点为root时会取到端点,即左(右)子树为空的情况,此时得分=左子树得分*1+根的分数,即给端点情况(实际这样的子树不存在)赋值为1
3、遍历顺序: 区间长度->区间起点->区间内选取根
4、区间起点的遍历n-i+1为结束,不要忘了 +1

5、区间内选取根两端点要取到

思路参考             代码参考

#include 
#include
#include
using namespace std;
int n,a[31];
long long dp[27][27],r[27][27];//dp[i][j]表示中序遍历序列从i到j的最高得分
void print(int i,int j)
{
	if (i>j)
		return ;
	cout<>n;
   memset(dp,0,sizeof(0));
   for (i=1;i<=n;i++)
   {
	   cin>>a[i];
	   dp[i][i]=a[i];
	   dp[i][i-1]=1;dp[i+1][i]=1;//防止左子树(或右子树)为空的情况(虽然这个意义上的dp不存在)
	   r[i][i]=i;//初始为叶子节点(每个都为根)
   }
   for (i=2;i<=n;i++)//区间长度
	   for (j=1;j<=n-i+1;j++)//区间起点,注意截止是n-i+1
		   for (k=j;k<=j+i-1;k++)//取区间内一点为root,注意必须两头都要取到!
			   if (dp[j][j+i-1]

题目链接:

题目大意:n堆石子围成圈,每次合并得分为合并后石子的重量,求合并成一堆石子后的最大得分和最小得分

思路:    思路参考
1、将前n-1堆石子挪到第n堆后面,则考虑合并2n-1堆石子

2、dp[i][j]是将第i到第j堆合并为一堆的总得分

注意:
1、 dp[i][i]初始为0
2、遍历的区间,len为[2,n],i为[1,2n-1], k为[i,j-1],k必须小于j, 不允许出现dp[i+1][i]的不合法情况

3、最终结果还需一重循环,是dp[i][i+n-1]的最大值

#include 
#include
#include
using namespace std;
int n,a[101];
int dp[201][201],fp[201][201];//dp[i][j]将第i到第j堆合并为一堆的总得分
int sum[201];//保存第1到第i堆石子的和
int main()
{
   int len,j,k,i;
   cin>>n;
   memset(dp,0,sizeof(dp));
   memset(fp,0x3f,sizeof(fp));
   memset(sum,0,sizeof(sum));
   for (i=1;i<=n;i++)
   {
	   cin>>a[i];
	   dp[i][i]=0;fp[i][i]=0;//一堆没有合并,初始为1
	   sum[i]=sum[i-1]+a[i];
   }
   for (i=1;i<=n-1;i++)//将前n-1堆石子挪到第n堆后面
   {
	   dp[n+i][n+i]=0;fp[n+i][n+i]=0;
	   sum[n+i]=sum[n+i-1]+a[i];
   }
   for (len=2;len<=n;len++)//区间长度
	   for (i=1;i<=2*n-1;i++)
	   {
			j=len+i-1;
			if (j>2*n-1)	break;
			for (k=i;k

Brackets  括号匹配问题
题意:给出一个的只有'(',')','[',']'四种括号组成的字符串,求最多有多少个括号满足题目里所描述的完全匹配。
·空串是完全匹配
·若s是完全匹配,则(s)和[s]是;若a和b是完全匹配,则ab是

思路:dp[i][j]表示[i,j]里最大完全匹配数。

//不知道对不对,没找到可以评测的oj,哪位小伙伴知道可以告知我一声谢谢!!
#include 
#include
#include
#include
using namespace std;
int dp[101][101];
int main()
{
	string s;
	int len,j,k,i,n;
	while(cin>>s)
	{
		if (s=="end")
			break;
		n=s.length();
		s=" "+s;//s[1...n]
		memset(dp,0,sizeof(dp));
		for (len=2;len<=n;len++)
			for (i=1;i<=n;i++)
			{
				j=len+i-1;
				if (j>n)	break;
				if ((s[i]=='(' && s[j]==')') ||(s[i]=='[' && s[j]==']'))//若匹配
					dp[i][j]=dp[i+1][j-1]+2;
				for (k=i+1;k

括号匹配变形及输出:百练 1141:Brackets Sequence

题目大意:找出以输入序列为子序列的最短合法序列。如 ([(] 的答案是 ()[()]

思路解释: 思路参考
d[i][j]从i到j最少需要加多少括号才能成为合法序列。

c[i][j]为输入序列从下标i到下标j的断开位置,如果没有断开则为-1

代码理解有点困难,我举个栗子:‘([))’,则dp[1][3]=1,dp[1][4]=2,c[1][4]=3,而dp[2][3]=2,即'[)',dp[1][4]==dp[2][3],则不会进入c[i][j]=-1。

而dp[1][3]在k的遍历中得3,dp[2][2]=1

含义是,从内层开始找,最先找到的不匹配是解决的最小子问题(子问题解决了后面的不匹配也就解决了),即所求的最少的增加括号数

注意:
1、初始化dp[i][i]=1

2、匹配后加判断dp[i][j]>dp[i+1][j-1]才能确定c[i][j]=-1

AC代码:

#include 
#include
#include
#define MAX 10000;
using namespace std;
string s;
int dp[101][101],c[101][101];
void print(int i,int j)
{
	if (i>j)return ;
	if (i==j && (s[i]=='(' || s[j]==')'))
	{
		cout<<"()";return;
	}
	else if (i==j &&(s[i]=='[' || s[j]==']') )
	{
		cout<<"[]";		return;
	}
	else if (c[i][j]!=-1)
	{
		print(i,c[i][j]);
		print(c[i][j]+1,j);
	}
	else//c[i][j]==-1,即s[i],s[j]是最小的完美匹配
	{
		if (s[i]=='(')
		{
			cout<<"(";print(i+1,j-1);cout<<")";//抵消(),输出中间的
		}
		else
		{ cout<<"[";print(i+1,j-1);cout<<"]";}
	}
}
int main()
{
	int len,j,k,i,n;
	cin>>s;
	n=s.length();
	s=" "+s;
	for (i=1;i<=n;i++)//初始化,每个单独的括号都是1
		dp[i][i]=1;
	for (len=2;len<=n;len++)
		for (i=1;i<=n;i++)
		{
			j=i+len-1;
			if (j>n)	break;
			dp[i][j]=MAX;
			for (k=i;k				if (dp[i][j]>dp[i][k]+dp[k+1][j])
				{
					dp[i][j]=dp[i][k]+dp[k+1][j];
					c[i][j]=k;
				}
			if ((s[i]=='(' && s[j]==')') ||(s[i]=='[' && s[j]==']'))//若匹配
				if (dp[i][j]>dp[i+1][j-1])//s[i],s[j]是匹配的最小子问题
				{
					dp[i][j]=dp[i+1][j-1];
					c[i][j]=-1;
				}
		}
	print(1,n);
	cout<

你可能感兴趣的:(dp)