*******动态规划********

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/LMengi000/article/details/81566690

目录

动态规划举例:

POJ 1163 The Triangle

百炼  2757:最长上升子序列

POJ 1458 Common Subsequence

4152:最佳加法表达式

 2755:神奇的口袋

 POJ 3624 Charm Bracelet

 1088:滑雪

 POJ 1390 Blocks

百炼 4149:课程大作业

扫描二维码关注公众号,回复: 2941015 查看本文章

 


动态规划举例:

楼梯有n个台阶,上楼可以一步上1阶,也可以一步上2阶,一共有多少种上楼的方法?

剩一个台阶的时候,有一种方法,f(1)=1;

剩两个台阶的时候,有两种方法,f(2)=2;

当剩下三个台阶的时候,就有三种方法.......这样就可以写出递推公式:f(n)=f(n-1)+f(n-2)(n>2)

通过这一个下题目,总结一下做动态规划的思路 :

1.需要一个记忆化数组。

2:根据递归定义,找到递推公式

 思路:先走一步,看剩下的。

 递推公式:初值是递归出口。

                   递推公式是递归体

f1=1,f2=2 ,f=f1+f2;

f1=f2,f2=f,f=f1+f2;

............

POJ 1163 The Triangle

Description

7
3   8
8   1   0
2   7   4   4
4   5   2   6   5

(Figure 1)

Figure 1 shows a number triangle. Write a program that calculates the highest sum of numbers passed on a route that starts at the top and ends somewhere on the base. Each step can go either diagonally down to the left or diagonally down to the right. 

Input

Your program is to read from standard input. The first line contains one integer N: the number of rows in the triangle. The following N lines describe the data of the triangle. The number of rows in the triangle is > 1 but <= 100. The numbers in the triangle, all integers, are between 0 and 99.

Output

Your program is to write to standard output. The highest sum is written as an integer.

Sample Input

5
7
3 8
8 1 0 
2 7 4 4
4 5 2 6 5

Sample Output

30

Source

IOI 1994

利用递归,存在大量的重复计算,例如: 数字三角形,从上往下扫描,

7 (1)  --->1
3(1) 8(1)----->1+1=2
8(1) 1(2) 0(1)---->1+2+1=4
2(1) 7(3) 4(3) 4(1)-------->1+3+3+1=8
4(1) 5(4) 2(6) 6(4) 5(1)------->1+4+6+4+1=16

1+2+4+6+8+16+......+n的平方=2的n次方,当n=100的时候,肯定会超时

7

3 8

8 1 0

2 7 4 4

4 5 2 6 5

1.7找3和8各一次---->7是 1

2.3找8和1各一次,8找1和0各一次 ,这样1就找了2次----->3是1,8是1,

/*递归代码::超时*/
#include<stdio.h>
#include<stdlib.h>
#include<iostream>
#include<algorithm>
#include<string.h>
#include<queue>
using namespace std;
const int maxn=101;
int n;
int D[maxn][maxn];
int MaxSum(int i,int j)
{
	if(i==n)
	{
		return D[i][j];
	}
	int x=MaxSum(i+1,j);
	int y=MaxSum(i+1,j+1);
	return max(x,y)+D[i][j];
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=i;j++)
		{
			scanf("%d",&D[i][j]);
		}
	}
	printf("%d\n",MaxSum(1,1));
}

 递推:

1.需要一个记忆化数组,来存放最大值,这样直接拿来用即可,不需要再扫描一次。

2.

if ( r == N)
MaxSum(r,j) = D(r,j)  //递归出口就是递推初值
else
MaxSum( r, j) = Max{ MaxSum(r+1,j), MaxSum(r+1,j+1) }+ D(r,j)//递归体就是递推公式

/*动归 */
#include<stdio.h>
#include<stdlib.h>
#include<iostream>
#include<algorithm>
#include<string.h>
#include<queue>
using namespace std;
const int maxn=101;
int n;
int maxSum[maxn][maxn],D[maxn][maxn];
int main()
{
	 scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=i;j++)
		{
			scanf("%d",&D[i][j]);
		}
	}
	for(int i=1;i<=n;i++)
	{
		maxSum[n][i]=D[n][i]; //递推初值
	}
	for(int i=n-1;i>=1;i--)
	{
		for(int j=1;j<=i;j++)
		{
			maxSum[i][j]=max(maxSum[i+1][j],maxSum[i+1][j+1])+D[i][j];//递推公式
              //maxSum里面已经存放了最大值,所以往上扫描的时候直接用就好了。
		}
	}
	printf("%d\n",maxSum[1][1]);
	return 0;
}

 注意:

1.递推初值。

2.递推状态方程。

百炼  2757:最长上升子序列

描述

一个数的序列bi,当b1 < b2 < ... < bS的时候,我们称这个序列是上升的。对于给定的一个序列(a1a2, ..., aN),我们可以得到一些上升的子序列(ai1ai2, ..., aiK),这里1 <= i1 < i2 < ... < iK <= N。比如,对于序列(1, 7, 3, 5, 9, 4, 8),有它的一些上升子序列,如(1, 7), (3, 4, 8)等等。这些子序列中最长的长度是4,比如子序列(1, 3, 5, 8).

你的任务,就是对于给定的序列,求出最长上升子序列的长度。

输入

输入的第一行是序列的长度N (1 <= N <= 1000)。第二行给出序列中的N个整数,这些整数的取值范围都在0到10000。

输出

最长上升子序列的长度。

样例输入

7
1 7 3 5 9 4 8

样例输出

4

来源

翻译自 Northeastern Europe 2002, Far-Eastern Subregion 的比赛试题

1 7 3 5 9 4 8

1.从头开始,到第i个数字结束,扫描一遍,maxlen[i]=以第i个数字结束的最长序列长度。

2.当到最后一个数字的时候,也是从头开始扫描一次,在前面符合条件的序列中再加上最后一个数字,修改最长序列长度,从这些长度中找出最长的序列长度,然后输出。

3. maxlen数组中存放的分别是第i个数字结尾的最长序列的长度,所以需要对序列长度排序然后输出最大的长度就好了。

#include<stdio.h>
#include<stdlib.h>
#include<iostream>
#include<algorithm>
#include<string.h>
#include<queue>
using namespace std;
const int maxn=11000;
int a[maxn],maxlen[maxn];
int main()
{
	int n;
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
		maxlen[i]=1;
	}
	for(int i=2;i<=n;i++)//以第i个数字为结尾的最长上升子序列 
	{
		for(int j=1;j<i;j++) //检查以第j个数为终点的最长上升子序列 
		{
			if(a[i]>a[j])
			{
			maxlen[i]=max(maxlen[i],maxlen[j]+1);	
		//	printf("i=%d maxnlen = %d \n",i,maxlen[i]);
			}
		}
	}
	cout<<* max_element(maxlen+1,maxlen+n+1);//max_element中存放的是数字的地址,要取出数组中内容,用 * 
	return 0;
}

注意:

1.明白递推的过程。

2.步骤清晰。

3.注意状态的含义,明白状态转换的过程。

4.maxlen数组存放数据的转变

POJ 1458 Common Subsequence

Description

A subsequence of a given sequence is the given sequence with some elements (possible none) left out. Given a sequence X = < x1, x2, ..., xm > another sequence Z = < z1, z2, ..., zk > is a subsequence of X if there exists a strictly increasing sequence < i1, i2, ..., ik > of indices of X such that for all j = 1,2,...,k, xij= zj. For example, Z = < a, b, f, c > is a subsequence of X = < a, b, c, f, b, c > with index sequence < 1, 2, 4, 6 >. Given two sequences X and Y the problem is to find the length of the maximum-length common subsequence of X and Y.

Input

The program input is from the std input. Each data set in the input contains two strings representing the given sequences. The sequences are separated by any number of white spaces. The input data are correct.

Output

For each set of data the program prints on the standard output the length of the maximum-length common subsequence from the beginning of a separate line.

Sample Input

abcfbc         abfcab
programming    contest 
abcd           mnp

Sample Output

4
2
0

Source

Southeastern Europe 2003

把s1与s2这两个字符串倒着看。

abcfbc  

abfcab

假设s1与s2字符串中变红的区域为已经查找结束最长公共序列的长度,那么再继续查找各自剩下的最后一个字符是否相同,不相同的情况下:s1:abcfbc,s2:abfca 这样两个字符串找公共序列最长长度 ; s1:abcfb ,s2:abfcab这样两个字符串找公共序列最长长度;再这样两个最长长度中再找最大值。

所以在做题的过程中,正着开始查找相同的字符,相同就在最大长度中+1,不相同就按照以上策略继续查找。

 

 

#include<stdio.h>
#include<stdlib.h>
#include<iostream>
#include<algorithm>
#include<string.h>
#include<queue>
using namespace std;
const int maxn=1100;
char s1[maxn],s2[maxn];
int maxlen[maxn][maxn];
int main()
{
	while(scanf("%s%s",s1,s2)!=EOF)
	{
		 int len1=strlen(s1);//字符串1的长度 
		 int len2=strlen(s2);//字符串2的长度 
		 for(int i=0;i<=len1;i++)
		  maxlen[i][0]=0;//状态初值:s1的前i个字符与s2的前0个字符相同字符个数为0 
		 for(int j=0;j<=len2;j++)
		  maxlen[0][j]=0;//状态初值:s1的前0个字符与s2的前j个字符相同字符个数为0 
		 for(int i=1;i<=len1;i++)
		 {
		 	for(int j=1;j<=len2;j++)
		 	{
		 		if(s1[i-1]==s2[j-1])//如果s1的第i-1个字符与s2的第j-1个字符相同 
		 		{
		 		  maxlen[i][j]=maxlen[i-1][j-1]+1;//更新公共序列的长度 
				}
				else//从s1的前i个字符与s2的前j-1个字符 
				{//从s1的前i-1个字符与s2的前j个字符  取最大值 
					maxlen[i][j]=max(maxlen[i][j-1],maxlen[i-1][j]);
				}
			 }
		 }
		 printf("%d\n",maxlen[len1][len2]);
	}
	return 0;
}

4152:最佳加法表达式

描述

给定n个1到9的数字,要求在数字之间摆放m个加号(加号两边必须有数字),使得所得到的加法表达式的值最小,并输出该值。例如,在1234中摆放1个加号,最好的摆法就是12+34,和为36

输入

有不超过15组数据
每组数据两行。第一行是整数m,表示有m个加号要放( 0<=m<=50)
第二行是若干个数字。数字总数n不超过50,且 m <= n-1

输出

对每组数据,输出最小加法表达式的值

样例输入

2
123456
1
123456
4
12345

样例输出

102
579
15

提示

要用到高精度计算,即用数组来存放long long 都装不下的大整数,并用模拟列竖式的办法进行大整数的加法。

来源

Guo Wei

递归: 会超时

#include<stdio.h>
#include<stdlib.h>
#include<iostream>
#include<algorithm>
#include<string.h>
#include<queue>
using namespace std;
#define INF 0x3f3f3f3f
const int maxn=1000;
int m,n;
int a[maxn];
char c[maxn];
int num[maxn][maxn],v[maxn][maxn];
int V(int m,int n)
{
	if(m==0)
	{
		return num[1][n]; //递归出口 
	}
	else if(n<m+1)
	{
		return INF;//递归出口  
	}
	else
	{
		int t=INF;
		for(int j=m;j<=n;j++)
		{
			t=min(t,V(m-1,j)+num[j+1][n]);
		}
		return t;
	}
}
int main()
{
	int cnt=1;
	while(cnt<=15)
	{
		scanf("%d",&m);
		scanf("%s",&c);
		n=strlen(c);
		for(int i=0;i<n;i++)
		{
			a[i+1]=c[i]-'0';
		}
		for(int i=1;i<=n;i++)
		{
			for(int j=1;j<=n;j++)
			{
				num[i][j]=INF;
			}
		}
		for(int i=1;i<=n;i++)
		{
			for(int j=1;j<=n;j++)
			{
				if(i==j)
				{
				  num[i][j]=a[j];	
				}else if(i<j)
				{ 
				  num[i][j]=num[i][j-1]*10+a[j];
				}
			}
		}
       printf("%d\n",V(m,n));
		cnt++;
	}
	return 0;
}

 2755:神奇的口袋

描述

有一个神奇的口袋,总的容积是40,用这个口袋可以变出一些物品,这些物品的总体积必须是40。John现在有n个想要得到的物品,每个物品的体积分别是a1,a2……an。John可以从这些物品中选择一些,如果选出的物体的总体积是40,那么利用这个神奇的口袋,John就可以得到这些物品。现在的问题是,John有多少种不同的选择物品的方式。

输入

输入的第一行是正整数n (1 <= n <= 20),表示不同的物品的数目。接下来的n行,每行有一个1到40之间的正整数,分别给出a1,a2……an的值。

输出

输出不同的选择物品的方式的数目。

样例输入

3
20
20
20

样例输出

3

从j个物品中凑出体积i

体积i的最大容量是40,物品j的数量是n,

1.当体积为0的时候,不论有多少物品都不能放,这是一种方法,也是唯一的一种;

2.当体积是大于0 的时候,这个时候就会有物品的选择,不选或者选。 

(1)当不选当前第j个物品的时候,在体积i一定的前提下,第j个物品中存放的物品选择种数与 放第j个物品之前的选择种数相同。

(2)当选择当前第j个物品的时候,体积为i的情况下:物品的种数是:选择了第j个物品,体积i就要减去当前第j个物品的体积(当前体积-物品j的体积=剩下体积),已经选择了第j个物品,就要用j-1个物品去凑剩下体积的种数 + 不选择第j个物品时的种数=第j个物品去凑i个体积的种数(用前j-1个物品去凑剩下体积) 

 递归:

#include<stdio.h>
#include<stdlib.h>
#include<iostream>
#include<algorithm>
#include<string.h>
#include<queue>
using namespace std;
int a[50];
int n;
int ways(int weight,int k)
{
	if(weight==0)
	{
		return 1;
	}
	if(k<0)
	{
		return 0;
	}
	return ways(weight,k-1)+ways(weight-a[k],k-1);//第k个物品选与不选 
}
int main()
{
	scanf("%d",&n);
	for(int i=0;i<n;i++)
	{
		scanf("%d",&a[i]);
	}
	printf("%d\n",ways(40,n-1));
	return 0;
}

 动态规划:

#include<stdio.h>
#include<stdlib.h>
#include<iostream>
#include<algorithm>
#include<string.h>
#include<queue>
using namespace std;
int a[50];
int n;
int ways[50][40];//从j个物品中凑出体积i 
int main()
{
	scanf("%d",&n);
	memset(ways,0,sizeof(ways));
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
		ways[0][i]=1;
	}
	ways[0][0]=1;
	for(int i=1;i<=40;i++)//i个体积 
	{
		for(int j=1;j<=n;j++)//j个物品 
		{
		ways[i][j]=ways[i][j-1];//第j个物品不选,种数和之前一样 
		if(i-a[j]>=0)  //如果体积装的下的话,就可以选 
		 ways[i][j]+=ways[i-a[j]][j-1];//第j个物品选择,体积减少,种数是之前种数加 1 
		}
	}
	for(int i=0;i<=40;i++)
	{
		printf("i= %d ",i);
		for(int j=0;j<=n;j++)
		{
			printf("%d ",ways[i][j]);
		}
		cout<<endl;
	}
	printf("%d\n",ways[40][n]);
	return 0;
}

注意:

1.初值的设置。

2.在做递归的时候,注意边界条件的判断。

3.在用动态规划做的时候,注意边界值ways[0][0]的设置。当没有体积没有物品的时候,这个时候种数就一种 :不选择。

 POJ 3624 Charm Bracelet

Description

Bessie has gone to the mall's jewelry store and spies a charm bracelet. Of course, she'd like to fill it with the best charms possible from the N (1 ≤ N ≤ 3,402) available charms. Each charm i in the supplied list has a weight Wi (1 ≤ Wi ≤ 400), a 'desirability' factor Di (1 ≤ Di ≤ 100), and can be used at most once. Bessie can only support a charm bracelet whose weight is no more than M (1 ≤ M ≤ 12,880).

Given that weight limit as a constraint and a list of the charms with their weights and desirability rating, deduce the maximum possible sum of ratings.

Input

* Line 1: Two space-separated integers: N and M
* Lines 2..N+1: Line i+1 describes charm i with two space-separated integers: Wi and Di

Output

* Line 1: A single integer that is the greatest sum of charm desirabilities that can be achieved given the weight constraints

Sample Input

4 6
1 4
2 6
3 12
2 7

Sample Output

23

Source

USACO 2007 December Silver

1.由于数组的取值范围太大,到12880,数组开的太大容易超时,所以不能用二维数组来做。

2.当前价值由上方价值与左上方价值所决定的,所以用滚动数组会节省空间。

见这个 

滚动数组:

#include<stdio.h>
#include<stdlib.h>
#include<iostream>
#include<algorithm>
#include<string.h>
#include<queue>
using namespace std;
#define MAXN 3402
#define MAXM 12880
int main(){
    int N, WW, W[MAXN+5], D[MAXN+5], dp[MAXM+5];
    while(scanf("%d%d", &N, &WW) != EOF){
        for(int i=0; i<N; i++){
            scanf("%d%d", &W[i], &D[i]);
         }
         memset(dp, 0, sizeof(dp));
         for(int i=0; i<N; i++){
             for(int j=WW; j>=W[i]; j--){
                 dp[j] = max(dp[j-W[i]]+D[i], dp[j]);
             }
         }
         printf("%d\n", dp[WW]);
     }
     return 0;
}

 记忆化数组,

#include<stdio.h>
#include<stdlib.h>
#include<iostream>
#include<algorithm>
#include<string.h>
#include<queue>
using namespace std;
const int maxn=1e5+10;
int n,W;   //用n个物品凑出W的重量 
int w[500],v[200];
int value[3000][3000];
int main()
{
	scanf("%d%d",&n,&W);// n个物品 W个重量 
	for(int i=0;i<n;i++)
	{
		scanf("%d%d",&w[i],&v[i]);//物品的 重量 和 价值 
	}
	memset(value,0,sizeof(value));
	for(int i=0;i<n;i++)  //从i个物品中挑选j个重量 
	{
		for(int j=0;j<=W;j++)
		{
			 if(j<w[i])
			{
				value[i+1][j]=value[i][j];
			}
			else
			{
				value[i+1][j]=max(value[i][j],value[i][j-w[i]]+v[i]);
			}
		}
	}
	printf("%d\n",value[n][W]);
	return 0;
}

递归:递归  超时,存在太多的重复计算 

#include<stdio.h>
#include<stdlib.h>
#include<iostream>
#include<algorithm>
#include<string.h>
#include<queue>
using namespace std;
const int maxn=1e4+10;
int n,W;
int w[150],v[150];
int solve(int i,int j)
{
	if(i==n)  //如果已经没有物品了,也就没有价值了 
	{
	   return 0;	
	}
	else if(j<w[i]) //如果当前物品的重量大于j,就去试放下一个物品 
	{
	   return solve(i+1,j);
	}
	else
	{//在不选择和选择中找最大价值返回 
		return max(solve(i+1,j),solve(i+1,j-w[i])+v[i]); 
	}
}
int main()
{
	scanf("%d%d",&n,&W);
	for(int i=0;i<n;i++)
	{
		scanf("%d%d",&w[i],&v[i]);
	}
	cout<<solve(0,W)<<endl;
	return 0;
}

注意:

1.数组范围。

2.

 1088:滑雪

描述

Michael喜欢滑雪百这并不奇怪, 因为滑雪的确很刺激。可是为了获得速度,滑的区域必须向下倾斜,而且当你滑到坡底,你不得不再次走上坡或者等待升降机来载你。Michael想知道载一个区域中最长的滑坡。区域由一个二维数组给出。数组的每个数字代表点的高度。下面是一个例子

 1  2  3  4 5
16 17 18 19 6
15 24 25 20 7
14 23 22 21 8
13 12 11 10 9


一个人可以从某个点滑向上下左右相邻四个点之一,当且仅当高度减小。在上面的例子中,一条可滑行的滑坡为24-17-16-1。当然25-24-23-...-3-2-1更长。事实上,这是最长的一条。

输入

输入的第一行表示区域的行数R和列数C(1 <= R,C <= 100)。下面是R行,每行有C个整数,代表高度h,0<=h<=10000。

输出

输出最长区域的长度。

样例输入

5 5
1 2 3 4 5
16 17 18 19 6
15 24 25 20 7
14 23 22 21 8
13 12 11 10 9

样例输出

25

来源 

Don't know

 1.人人为我型

每一个高度的初值都为1,将滑雪的高度从小到大排列,从最大的点开始遍历它上下左右这四个点,看哪一个方向的高度比当前的高度小,小的话就证明可以滑下去;如果找到比当前高度小的高度,就更新高度小的数组,所以遍历完之后,最大高度应存放在最低高度数组里。

#include<stdio.h>
#include<stdlib.h>
#include<iostream>
#include<algorithm>
#include<string.h>
using namespace std;

int R,C;
int g[200][200];
int p[200][200];
int dir[4][2]={1,0,0,1,0,-1,-1,0};
struct Node
{
	int l,r;
	int h;
	bool operator<(const Node & p) const
	{
		return h<p.h;
	}
}s[10100];
int main()
{
   scanf("%d%d",&R,&C);
   int w=0;
   for(int i=1;i<=R;i++)
   {
   	for(int j=1;j<=C;j++)
   	{
   		scanf("%d",&p[i][j]);
   	   	g[i][j]=1;
   	   	s[w].l=i;
   	   	s[w].r=j;
   	   	s[w].h=p[i][j];
   	   	w++;
	}
   }
   sort(s,s+w);
   int ans=1;  //注意 
   for(int i=w-1;i>=0;i--)
   {
   	int x=s[i].l;
   	int y=s[i].r;
   	 for(int j=0;j<4;j++)
   	 {
   		int fx=x+dir[j][0];
   		int fy=y+dir[j][1];
   		if(fx>=1 && fx<=R && fy>=1&&fy<=C && p[fx][fy]<p[x][y])
   		{
   		    g[fx][fy]=max(g[fx][fy],g[x][y]+1);
   		    ans=max(ans,g[fx][fy]);
		}
	 }
   }
   printf("%d\n",ans);
   return 0;
}

 

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int r,c;
struct Node{
int a;
int x;
int y;
bool operator < (const Node &s) const{
        return a<s.a;
    }
}nodes[10100]; 
int a[110][100],b[110][110];
int main(){
cin>>r>>c;
int cnt=0,ans=1;
for(int i=1;i<=r;i++){
for(int j=1;j<=c;j++){
scanf("%d",&a[i][j]);
nodes[cnt].a=a[i][j];
b[i][j]=1;
nodes[cnt].x=i;
nodes[cnt].y=j;
cnt++;
}
}
sort(nodes,nodes+cnt);
for(int i=0;i<cnt;i++){//我为人人 
int x=nodes[i].x;
int y=nodes[i].y;
if(x+1<=r&&a[x][y]<a[x+1][y]){
b[x+1][y]=max(b[x+1][y],b[x][y]+1);
ans=max(ans,b[x+1][y]);
}
if(y+1<=c&&a[x][y]<a[x][y+1]){
b[x][y+1]=max(b[x][y+1],b[x][y]+1);
ans=max(ans,b[x][y+1]);
}
if(x-1>=1&&a[x][y]<a[x-1][y]){
b[x-1][y]=max(b[x-1][y],b[x][y]+1);
ans=max(ans,b[x-1][y]);
}
if(y-1>=1&&a[x][y]<a[x][y-1]){
b[x][y-1]=max(b[x][y-1],b[x][y]+1);
ans=max(ans,b[x][y-1]);	
}
 //printf("ans=%d\n",ans);
}
cout<<ans<<endl;
return 0;
}

 注意:

1.ans的初值不能为-1或者为0,只能为1,当只有一个点的时候,就不会进入循环(ans=max(ans,g[fx][fy]);),所以输出的ans的值就是不合法的,最小要滑雪的距离也得是1.

2.注意边界值的判断。

 POJ 1390 Blocks

Description

Some of you may have played a game called 'Blocks'. There are n blocks in a row, each box has a color. Here is an example: Gold, Silver, Silver, Silver, Silver, Bronze, Bronze, Bronze, Gold. 
The corresponding picture will be as shown below: 

 
Figure 1


If some adjacent boxes are all of the same color, and both the box to its left(if it exists) and its right(if it exists) are of some other color, we call it a 'box segment'. There are 4 box segments. That is: gold, silver, bronze, gold. There are 1, 4, 3, 1 box(es) in the segments respectively. 

Every time, you can click a box, then the whole segment containing that box DISAPPEARS. If that segment is composed of k boxes, you will get k*k points. for example, if you click on a silver box, the silver segment disappears, you got 4*4=16 points. 

Now let's look at the picture below: 

 
Figure 2



The first one is OPTIMAL. 

Find the highest score you can get, given an initial state of this game. 

Input

The first line contains the number of tests t(1<=t<=15). Each case contains two lines. The first line contains an integer n(1<=n<=200), the number of boxes. The second line contains n integers, representing the colors of each box. The integers are in the range 1~n.

Output

For each test case, print the case number and the highest possible score.

Sample Input

2
9
1 2 2 2 2 3 3 3 1
1
1

Sample Output

Case 1: 29
Case 2: 1

Source

Liu Rujia@POJ

区间DP 

 点击单个片段的分数将相同颜色片段连接再点击所得分数最大值。

从i到j-1寻找与第j个颜色相同片段,找到之后,就做拼接,把颜色相同的长度计算出来,并且把颜色相同的片段之间的块的长度(知道长度就知道分数)算出来,最后相加取最大值

#include<stdio.h>
#include<stdlib.h>
#include<iostream>
#include<algorithm>
#include<string.h>
#include<queue>
#include<set>
#include<math.h>
using namespace std;
const int M=210;
int score[M][M][M];
struct segment
{
	int color;
	int len;
}segments[M];
int ClickBox(int i,int j,int len)
{
	if(score[i][j][len]!=-1)
	   return score[i][j][len];
	int result=(segments[j].len+len)*(segments[j].len+len);
	if(i==j)
	return result;
	result+=ClickBox(i,j-1,0); //点击单个片段的总得分
	for(int k=i;k<=j-1;++k)  //从前j-1块中查找与第j块颜色相同的片段
	{
		if(segments[k].color!=segments[j].color)
		continue;
		int r=ClickBox(k+1,j-1,0);//找到第k块与第j块颜色相同,就再从k+1到j-1这一片段中再次查找,又是一个新的查找过程
		r+=ClickBox(i,k,segments[j].len+len);//将第k块与第j块连接,算出连接之后的分数
		result=max(result,r);//将单个点击所得分数与拼接点击所得的分数比较取最大值
	}
	score[i][j][len]=result;//i到j这些块中,有len个长度的块的颜色与j的颜色相同
	return result;
}
int main()
{
	int T;
	cin>>T;
	for(int t=1;t<=T;t++)
	{
		int n;
		memset(score,0xff,sizeof(score));
		cin>>n;
		int segNum=-1;
		int  lastC=0;
		for(int i=0;i<n;i++)
		{
			int c;
			cin>>c;
			if(c!=lastC)
			{
				segNum++;
				segments[segNum].len=1;
				segments[segNum].color=c;
				lastC=c;
			}
			else
			{
				segments[segNum].len++;
			}
		}
		printf("Case %d: %d\n",t,ClickBox(0,segNum,0));
	}
	return 0;
}

 注意:

百炼 4149:课程大作业

描述

 

小明是北京大学信息科学技术学院三年级本科生。他喜欢参加各式各样的校园社团。这个学期就要结束了,每个课程大作业的截止时间也快到了,可是小明还没有开始做。每一门课程都有一个课程大作业,每个课程大作业都有截止时间。如果提交时间超过截止时间X天,那么他将会被扣掉X分。对于每个大作业,小明要花费一天或者若干天来完成。他不能同时做多个大作业,只有他完成了当前的项目,才可以开始一个新的项目。小明希望你可以帮助他规划出一个最好的办法(完成大作业的顺序)来减少扣分。

 

输入

输入包含若干测试样例。
输入的第一行是一个正整数T,代表测试样例数目。
对于每组测试样例,第一行为正整数N(1 <= N <= 15)代表课程数目。
接下来N行,每行包含一个字符串S(不多于50个字符)代表课程名称和两个整数D(代表大作业截止时间)和C(完成该大作业需要的时间)。
注意所有的课程在输入中出现的顺序按照字典序排列。

输出

对于每组测试样例,请输出最小的扣分以及相应的课程完成的顺序。
如果最优方案有多个,请输出字典序靠前的方案。

样例输入

2 
3 
Computer 3 3 
English 20 1 
Math 3 2 
3
Computer 3 3 
English 6 3 
Math 6 3

样例输出

2 
Computer 
Math 
English 
3 
Computer 
English 
Math

提示

第二个测试样例, 课程完成顺序Computer->English->Math 和 Computer->Math->English 都会造成3分罚分, 但是我们选择前者,因为在字典序中靠前.

状态压缩

把作业的每一种状态都要列举出来,并且用二进制的&运算来解决这个问题。
思路:作业最多15个,所以我们可以使用状态压缩,
用dp[i]表示已完成的作业为i时需要扣的分数
(i的第k个二进制位为1则表示第k个作业已经完成,为0则未完成)
dp[i] = min { dp[ i – (1 << k) ] + score(i,k) | i 的第k位为1 }
从作业i中完成第k个作业 + 完成第k个作业新增加的扣分 
 

 

#include<stdio.h>
#include<stdlib.h>
#include<iostream>
#include<algorithm>
#include<string.h>
#include<queue>
#include<set>
#include<math.h>
#include<vector>
using namespace std;
/*
思路:作业最多15个,所以我们可以使用状态压缩,
用dp[i]表示已完成的作业为i时需要扣的分数
(i的第k个二进制位为1则表示第k个作业已经完成,为0则未完成)
dp[i] = min { dp[ i – (1 << k) ] + score(i,k) | i 的第k位为1 }
从作业i中完成第k个作业 + 完成第k个作业新增加的扣分 
*/
int t;
int n;
struct HomWork
{
	string name;
	int d;//截止时间 
	int c;//完成作业要花的时间 
}hw[20];
struct Node
{
	int pre;//完现上一个作业时的状态 
	int minScore;//当前状态的分数 
	int last;// 当前状态下,最后完成的作业的编号 
	int finishDay;//作业last的完成时间 
}dp[(1<<16)+30];
vector<int>GetPath(int status)
{
	vector<int>path;
	while( status )
	{
		path.push_back(dp[status].last);
		status=dp[status].pre;
	}
	reverse(path.begin(),path.end());
	return path;
}
int main()
{
	scanf("%d",&t);
	while(t--)
	{
		scanf("%d",&n);
		char name[20];
		int d,c;
		for(int i=0;i<n;++i)
		 cin>>hw[i].name>>hw[i].d>>hw[i].c;
		 dp[0].finishDay=0;//将开始的第一个作业赋初值 
		 dp[0].minScore=0;
		 dp[0].pre=-1;
		 int m=1<<n;
         //printf("m = %d\n",m); 
		 for(int i=1;i<m;i++)//代表已完成的作业的集合 
		 {
		 	dp[i].minScore=1<<30;
		 	for(int j=0;j<n;j++)
		 	{
		 	//	printf("** = %d\n",i&(1<<j));
		 	   if(i&(1<<j)) //从集合i中完成了第j个作业
			  {
			     int pre=i-(1<<j); //完成前一个作业时的状态 
			     int finishDay=dp[pre].finishDay+hw[j].c; 
			     int tmpScore=finishDay-hw[j].d;
			     if(tmpScore<0)
			        tmpScore=0;
			     if(dp[i].minScore>dp[pre].minScore+tmpScore)
			     {
			        dp[i].minScore=dp[pre].minScore+tmpScore;
			        dp[i].pre=pre;
			        dp[i].finishDay=finishDay;
			        dp[i].last=j;
				 }
				 if(dp[i].minScore==dp[pre].minScore+tmpScore) 
				 {
				 	vector<int>p1=GetPath(dp[i].pre);
				 	vector<int>p2=GetPath(pre);
				 	if(p2<p1)
				 	{
				 	  dp[i].pre=pre;
					  dp[i].finishDay=finishDay;
					  dp[i].last=j;	
					}
				 }
			  } 	
			}
		 }
		 cout<<dp[m-1].minScore<<endl;
		 int status=m-1;
		 vector<int>path=GetPath(status);
		 for(int i=0;i<path.size();++i)
		  cout<<hw[path[i]].name<<endl;
	}
	return 0;
} 

猜你喜欢

转载自blog.csdn.net/LMengi000/article/details/81566690