动态规划---三题 石子归并,多边形 金字塔 0607

动态规划---三题

/*
石子合并问题---区间dp
区间dp板子:
枚举区间长度,枚举左端点,右端点根据左端点和长度得到
石子合并问题:将一堆顺序堆放的石子,每次只能相邻两堆进行合并,每次合并的代价是所合并两堆石子的重量和,求将n堆石子合并的最小代价;N上限为300;

暴力枚举,一共有n堆石子,
第一次合并,可选择的情况是n-1,n堆石子有n-1个间隔,合并的每种方案可以看作从n-1个间隔中选择一个;
第二次合并,有n-2种可能;
第n次合并,有n-n种可能;
所有可能情况是(n-1)!;

区间dp,集合:设f[i][j]的集合是表示合并区间[I,j]石子的所有方案;属性,设f[i][j]表示方案代价的最小值;递推关系:找最后一次不同,由于合并的石子一定是相邻的,那么f[i][j]表示的石子合并方案数可以根据最后一次合并的石子界限划分为f[i][k]和f[k+1][j],比如f[i][i],f[i+1][j];f[i][i+1],f[i+2][j];f[i][i+2],f[i+3][j];直到f[i][j-1],f[j][j];
f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]+s[j]-s[i-1])
枚举区间长度,枚举左端点,右端点根据左端点和长度得到
区间长度是1,不需要合并,代价为0,初始化是f[N][N]中每个元素都初始化为0,所以区间长度可以从2开始;
第一种书写:
f[i][j]={0};
For Len in [2,n]:
	For L in [1,n]:
	If L+Len-1 >n: break;
	R=L+Len-1;
	F[L,R]初始化为无限大---凡是用到min,都要这么做
	For k in  [L,R-1]:
		F[L,R]=min(f[L,R],f[L,k]+f[k+1,R]+s[R]-s[L-1])

第二种书写:
f[i][j]={无穷大};//凡是用到min,都要这么做
f[i][i]=0,对于所有的i=1.。。。n;//初始化
For Len in [2,n]:
	For L in [1,n]:
	If L+Len-1 >n: break;
	R=L+Len-1;
	For k in  [L,R-1]:
		F[L,R]=min(f[L,R],f[L,k]+f[k+1,R]+s[R]-s[L-1])



*/


#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>

using namespace std;
const int N = 310;
int f[N][N];
int a[N];
int s[N];
int n;
int main() {
	scanf("%d", &n);
	for (int i = 1; i <= n; i++)scanf("%d", &a[i]), s[i] = s[i - 1] + a[i];
	for (int len = 2; len <= n; len++)
		for (int L = 1; L <= n && L + len - 1 <= n; L++) {
			int R = L + len - 1;
			f[L][R] = 1 << 30;//初始化为无限大 
			for (int k = L; k < R; k++)
				f[L][R] = min(f[L][R], f[L][k] + f[k + 1][R] + s[R] - s[L - 1]);
		}

	printf("%d\n", f[1][n]);
	return 0;
}

/*
多边形
多边形是一种单人益智游戏
游戏开始时,给定玩家一个具有N个顶点N条边的多边形,
例如图四,其中N=4,每个顶点上有一个整数,每个边上有一个运算符,加号或者乘号

第一步,玩家选择一条边,将它删除,在接下来的n-1步,每一步中,玩家选择一条边,把这条边以及与这条边连接的两个顶点用一个新的顶点代替,新的顶点的上面的整数值等于删除的两个顶点上的数按照边上的运算符进行计算得到的结果

和石子归并类似,这个有一个环,破一个边,再在得到的链上进行相邻两个数之间的合并,合并不是简单的求和,而是做乘法或者做加法,
需要相邻两个数之间的操作,

一开始是环,进行断环成链,即原来是[1..n]区间,再复制一段,变成【1,2,3.。。。,n,1,2,3.。。,n】这样,从编号为3处断开,变成求解4,5,。。。。n,1,2,3

定义f[i][j]表示的集合是,区间[][j]之间数合并成一个数的所有可能方案集合
属性是最后得到的数的最大值,
转移方程是:从最后一步不同找起,假设最后一步合并的两个数的分界点是i,那么,就是求f[i][i]和f[i+1][j]两个数合并的最大值,区间[i][i]合并为一个数,区间[i+1][j]合并为一个数,如果运算符是加号,那么这两个区间合并得到的最大值相加即可,如果是乘号,需要考虑正负问题,如-10*-10=100>2*3=6,所以需要记录最小值,使用g[i][j]表示上面所表示集合的最小值,记minl=g[i][i],minr=g[i+1][j],maxl=f[i][i],maxr=f[i+1][j],
X1=minl*minr,x2=minl*maxr,x3=maxl*minr,x4=maxl*maxr
f[i][j]=max(f[i][j],x1,x2,x3,x4),g[i][j]=min(g[i][j],x1,x2,x3,x4)

对于min,初始化为无穷大32768,对于max,初始化为负无穷大-32768,
题目说明的顶点数值范围是[-32768,32768];
初始化,对于f[i][i]和g[i][i],初始化为相应位置的数即可

区间长度从2开始到n
左端点从1开始到右端点到达2n时
For len in [2,n]:
	For L in [1,n]:
	If L+len-1>n:
Break
	R=L+len-1;
	F[i][j]=32768,g[i][j]=-32768
	For k in [L,R-1]:
	If(w[k+1]是加号:
		F[i][j]=max(f[i][j],f[i][k]+f[k+1][j])
		G[i][j]=min(g[i][j],g[i][k]+g[k+1][j]
	Else:
		Maxl=f[i][k],maxr=f[k+1][j];
		Minl=g[i][k],minr=g[k+1][j]
		X1,x2,x3,x4=maxl*minr,maxl*maxr,minl*minr,minl*maxr;
		F[i][j]=max(f[i][j],x1,x2,x3,x4)
		G[i][j]=max(g[i][j],x1,x2,x3,x4)

输出最大的
F[i][i+n-1],i=1,2,3…,n
并记录res

同时,输出等于res的f[ii][ii+n-1]的相应ii




*/


#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>

using namespace std;
const int N = 110;
//a--数字,w运算符

int a[N],f[N][N],g[N][N];
char w[N]; 
int n;
int main(){
	scanf("%d",&n);
	for(int i = 1;i <= n;i++){
		//scanf("%c %d ",&w[i],&a[i]);
		cin>>w[i]>>a[i];
		w[i+n]=w[i];
		a[i+n]=a[i];
		f[i][i]=g[i][i]=f[i+n][i+n]=g[i+n][i+n]=a[i];
	}
	//for(int i = 1;i <= n;i++){
	//	printf("%c %d ",w[i],a[i]);
	//}
	for(int len = 2;len<= n;len++)
		for(int  L = 1;L+len-1<=2*n;L++)
		{
			int R= L+len-1;
			f[L][R]=-32769,g[L][R]=32769;
			for(int k = L; k<R;k++){
				int minl=g[L][k],minr=g[k+1][R],maxl=f[L][k],maxr=f[k+1][R];
				if(w[k+1]=='t'){
					f[L][R]=max(f[L][R],maxl+maxr);
					g[L][R]=min(g[L][R],minl+minr);
				}
				else{
					int x1=minl*minr,x2=minl*maxr,x3=maxl*minr,x4=maxl*maxr;
					f[L][R]=max(f[L][R],max(max(x1,x2),max(x3,x4)));
					g[L][R]=min(g[L][R],min(min(x1,x2),min(x3,x4)));
				}
			}	
		}	
	int res=-32769;
	for(int i = 1;i <= n;i++)res=max(res,f[i][i+n-1]);
	printf("%d\n",res);
	for(int i = 1;i <= n;i++)
		if(res==f[i][i+n-1]){
			printf("%d ",i);
		}
	printf("\n");
	return 0;
}

/*
多边形游戏是一款单人益智游戏
游戏开始时,给定玩家一个具有N个顶点N条边的多边形,编号1-N
每条边上有一个运算符,连接两个顶点,每个顶点上有一个整数,
第一步玩家选择一条边,并将其删除
在接下来的N-1步中,
在每一步,玩家选择一条边,把这条边以及该边连接的两个顶点用一个新的顶点代替,
新的顶点的数值等于删除的两个顶点在该边上的运算符计算下得到的结果数值,
最终,游戏仅剩下一个顶点,顶点上的数值就是玩家的得分
请计算,玩家最高的得分是多少,以及第一步有哪些策略可以使玩家获得最高分

将上述问题在第一步选择删除一条边,就是断环成链,复制一遍原数组,
原长为n改为长为2n,问题转化为求解从i开始,
长度为i+n-1的区间数字合并为一个数字的所有方案中,得到的最大数字,i从1开始直到n,
不妨取从2到2+n-1这个区间的数进行合并,求出所有合并方案的得到的最后一个数的最大值,
集合f[i][j]表示区间[i][j]之间所有合并成一个数的所有方案,
属性是所有方案得到的最后一个数的最大值,考虑最后一次的差异,
也是相邻两个数之间进行合并,分界点可以是k,其中k在[2,…,2+n-1  -1],
左半部分是[2,k],右半部分是[k+1,2+n-1],合并方式是存储在k+1位置,
设op存储所有的操作方式,那么合并方式就是op[k+1],
合并方式有两种,一种是求和,一种是求乘积,
对于求和这种合并方式,求最大值只需要左半部分取最大且右半部分取最大,
然后再加到一起即可,
对于求乘积这种合并方式,
求最大值需要考虑左半部分合并为一个数和右半部分合并为一个数,
这两个数的正负,由于可能存在负数,
设左半部分和右半部分得到的数的最大值和最小值得到的四个数minl,maxl,minr,maxr,
所求的最大值一定是左半部分和右半部分的乘积得到的四个数之间的最大值,
即,minl*minr,minl*maxr,maxl*minr,maxl*maxr中的最大值;
注意,上述引入了最小值,所以引入g[i][j]表示的集合和f[i][j]相同,
但是属性不一样,g[i][j]表示的属性是所有方案得到的最小的数。
初始化,对于f[i][i]和g[i][i],初始化为a[i],假设a[i]存储的是第i个数;
数据预处理:由于需要断环为链,对于给定的n个数和n个运算符,不仅要记录,
还要复制一份到相应数组的后面,从而将问题等价转化为求区间[i][i+n-1]之间的
所有合并方案得到的最大的数fi,在n种断链方式,对应i从1到n,求最大的fi和对应的i的值,输出



*/


#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>

using namespace std;
const int N = 110,INF=32769;
int point[N],f[N][N],g[N][N];
char op[N];
int n;
int main(){
	cin>>n;
	for(int i = 1 ;i <= n;i ++){
		cin>>op[i]>>point[i];
		op[i+n]=op[i];
		point[i+n]=point[i];
		f[i][i]=g[i][i]=g[i+n][i+n]=f[i+n][i+n]=point[i];
	}
	for(int len = 2; len <= n;len++)//区间长度从2到n,最长为n
		for(int L = 1;L + len - 1 <=2*n;L++){//枚举左端点,右端点不能超过2*n 
			int R=L+len-1;
			f[L][R]= -INF,g[L][R]=INF;
			for(int k = R; k>L;k--){
				int minL=g[L][k-1],maxL=f[L][k-1];
				int minR=g[k][R],maxR=f[k][R];
				if(op[k]=='t'){
					f[L][R]=max(f[L][R],maxL+maxR);
					g[L][R]=min(g[L][R],minL+minR);
				}
				else {
					int x1=minL*minR,x2=minL*maxR,x3=maxL*minR,x4=maxL*maxR;
					f[L][R]=max(f[L][R],max(max(x1,x2),max(x3,x4)));
					g[L][R]=min(g[L][R],min(min(x1,x2),min(x3,x4)));
				}
			}
		} 
	int res=-INF;
	for(int i = 1; i <= n;i++)res=max(res,f[i][i+n-1]);
	printf("%d\n",res);
	for(int i = 1;i <= n;i++)if(res==f[i][i+n-1])printf("%d ",i);
	return 0;
}

多边形游戏是一款单人益智游戏
游戏开始时,给定玩家一个具有N个顶点N条边的多边形,编号从1到N,
每个顶点上写着一个整数,每个边上标有一个运算符,加号或者乘号,
第一步,玩家选择一条边,将它删除,
在接下来的N-1步,在每一步中,玩家选择一条边,把这条边以及与这条边相连的2个顶点用一个新的顶点代替,新的顶点上的整数值是删去的;两个顶点按照删去的边上的符号进行运算得到的结果
最终,游戏只剩下一个顶点,顶点山的整数值就是玩家的得分,求玩家最高能获得多少分,请输出最高得分,输出得到最高得分的哪些第一步策略可以使玩家得到最高分

输入的数据是一个环,使用数组存储环的顶点和环的边,
使用point存储点上的整数数值,使用op存储边上的运算符;
输入的数据point[1],point[2],point[3],point[4]
op[1],op[2],op[3],op[4],
point[1]和point[2]之间的边是op[2]
point[2]和point[3]之间的边是op[3]
point[3]和point[4]之间的边是op[4]
断环成链

point[4]和point[1+4]两个顶点的对应边op[1+4],

point[5]和point[2+4]两个顶点的对应边op[2+4],

point[i]和point[i+1]之间的边是op[i+1]

区间[i][j]之间的符号存储在[i+1][j]---op,op[j+1]对应的点是point[j][j+1],j+1不在范围内;

理清索引下标的问题,上述问题使用断环成链,转化为n个问题的最大值,i=1,。。。,n,求区间[i][i+n-1]合并成一个数字的所有方案中最后得到的最大的数,该问题与石子合并是一致的,区别,石子合并是求和,相当于运算符都是加号,而此处不仅有加号,还有乘号,两个区间分别合并成一个数,这两个数再合并得到一个最大的数maxres,maxres可能来自两个区间的最大最小数乘积,共有四种情况,这就是引入乘号合并方式带来的新的考虑。



#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>

using namespace std;
const int N=110,INF=32768;
int point[N],f[N][N],g[N][N];
char op[N];
int n;
int main(){
	scanf("%d",&n);
	for(int i = 1;i <= n;i++){
		cin>>op[i]>>point[i];
		f[i][i]=f[i+n][i+n]=g[i][i]=g[i+n][i+n]=point[i];
		point[i+n]=point[i];
		op[i+n]=op[i];
	}
	for(int len =2 ;len <= n;len++)
		for(int L = 1;L + len - 1<= 2*n;L++){
			int R=L+len-1;
			f[L][R]=-INF,g[L][R]=INF;
			for(int k = L;k<R;k++){
				int maxL=f[L][k],minL=g[L][k],maxR=f[k+1][R],minR=g[k+1][R];
				if(op[k+1]=='x'){
					int x1=minL*minR,x2=minL*maxR,x3=maxL*minR,x4=maxL*maxR;
					f[L][R]=max(f[L][R],max(max(x1,x2),max(x3,x4)));
					g[L][R]=min(g[L][R],min(min(x1,x2),min(x3,x4)));
				}
				else{
					f[L][R]=max(f[L][R],maxL+maxR);
					g[L][R]=min(g[L][R],minL+minR);
				}
			}
		}
	int res=-INF;
	for(int i = 1;i <= n;i++)res=max(res,f[i][i+n-1]);
	printf("%d\n",res);
	for(int i = 1;i <= n;i++)if(res==f[i][i+n-1])printf("%d ",i);
	printf("\n");
	return 0;
}

/*
金字塔:
一棵树的dfs序
设一棵树有n个节点,则有n-1条边
对于根节点,访问第一次将输出一个字母;对于每条边,将输出两个字母,下去上来,
所以一共输出1+2*(n-1)个节点;
即输出2n-1个节点,奇数个节点

定义f[i][j]的集合是表示dfs序是str[i]…[j]的所有树的集合,属性是集合的数量
对于f[i][j],
找最后一个不同点进行划分,
最后一棵子树的根节点假设在位置k处,由于每经过一个点就会输出节点,
所以,最后一棵子树一定是的dfs序形如x*****x,其中x表示位置k处的字符,
即要求str[k]和str[j]相同,
此外,一棵子树的dfs序的长度一定是奇数,所以对于f[i][j],
k可能取值是j-k+1是奇数,如果i。。。j之间的长度是奇数的话,
那么k取值每次在i的基础上加2,要是一棵子树,还必须满足str[k]==str[j],
最后一颗子树的集合数量是f[k+1][j-1],
乘以前面的f[i][k]就是以str[k]为最后一颗子树根节点时的所有情况数,
k遍历取值求和。
由于f[k+1][j-1]表示的是最后一棵子树的所有可能的集合数,
所以最后一棵子树要存在,即k+1<=j-1,k<=j-2
k<j-1

*/


#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>

using namespace std;
typedef long long LL;
const int N=312,mod=1e9;
string str;
int f[N][N];
int main(){
	cin>>str;
	int n = str.size();
	if(n%2==0)printf("%d",0);
	else{
		for(int len=1;len<=n;len++)
			for(int L=0;L+len-1<n;L++){
				int R=L+len-1;
				if(len==1)f[L][R]=1;
				else if(str[L]==str[R]){
					for(int k = L;k<R-1;k+=2){
						if(str[k]==str[R])
							f[L][R]=(f[L][R]+(LL)f[L][k]*f[k+1][R-1])%mod;
					}
				}
			}
		printf("%d\n",f[0][n-1]);
	}
	return 0;
}

/*

杨老师的照相排列
有N个同学合影,站成左端对齐的k排,每排分别N1,N2,。。。,Nk个人
保证N1>=N2>=N3>=…>=Nk
第一排站在最后,第k排站在最前面,学生的身高各不相同,他们的身高从高到低记为1,2,3,。。。,N
在合影时要求每一排从左到右身高递减,每一列从后到前身高也递减
问一共有多少种合适的位置的方案数
最多有5排
轮廓表示法,
设第一排、第二排、第三排、第四排、第五排的人数分别是a,b,c,d,e
f[a][b][c][d][e]表示该轮廓
后一排的人数一定比前一排少,否则不满足身高递减
f[a][b][c][d][e]表示的集合:
每排分别是a,b,c,d,e且满足每排从左至右身高递减,每列从前到后身高递减的所有可能的方案的集合
属性:集合的数量

状态转移:考虑最后一个不同点,最后一个不同点应该找最矮的那个人,最矮的那个人的编号是x=a+b+c+d+e
因为最矮的那个人放到哪排的队尾都是可以的,
如果放到第一排的队尾,那么第一排的人数将变为a-1,由于这个人是最矮的,所以不会影响剩下其他排以及第一排的剩余人的排列,此时转变为状态f[a-1][b][c][d][e];

如果放到第二排的队尾,那么第二排的人数将变为b-1,由于这个人是最矮的,所以不会影响剩下其他排以及第二·排的剩余人的排列,此时转变为状态f[a][b-1][c][d][e];

如果放到第三排的队尾,那么第三排的人数将变为c-1,由于这个人是最矮的,所以不会影响剩下其他排以及第三排的剩余人的排列,此时转变为状态f[a][b][c-1][d][e];

如果放到第四排的队尾,那么第四排的人数将变为d-1,由于这个人是最矮的,所以不会影响剩下其他排以及第四排的剩余人的排列,此时转变为状态f[a][b][c][d-1][e];

如果放到第五排的队尾,那么第五排的人数将变为e-1,由于这个人是最矮的,所以不会影响剩下其他排以及第五排的剩余人的排列,此时转变为状态f[a][b][c][d][e-1];


以此类推,


*/



#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>

using namespace std;
typedef long long LL;
const int N=33;
int n;
LL f[N][N][N][N][N];
int s[N];
int main(){
	while(cin>>n,n){
		memset(s,0,sizeof(s));
		for(int i = 0;i<n;i++)cin>>s[i];
		memset(f,0,sizeof(f));
		f[0][0][0][0][0]=1;
		for(int a = 0;a <= s[0];a++)
			for(int b = 0;b <= min(a,s[1]);b++)
				for(int c = 0;c <= min(b,s[2]);c++)
					for(int d = 0;d <= min(c,s[3]);d++)
						for(int e = 0;e <= min(d,s[4]);e++){
							if(a&&a-1>=b)f[a][b][c][d][e]+=f[a-1][b][c][d][e];
							if(b&&b-1>=c)f[a][b][c][d][e]+=f[a][b-1][c][d][e];
							if(c&&c-1>=d)f[a][b][c][d][e]+=f[a][b][c-1][d][e];
							if(d&&d-1>=e)f[a][b][c][d][e]+=f[a][b][c][d-1][e];
							if(e)f[a][b][c][d][e]+=f[a][b][c][d][e-1];
						}
		printf("%lld\n",f[s[0]][s[1]][s[2]][s[3]][s[4]]);
	}
	return 0;
}

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值