非对称DP

目录

一,对称DP、非对称DP

1,对称DP

2,非对称DP

二,状态转换DP

POJ 3661 Running

力扣 188. 买卖股票的最佳时机 IV

力扣 121. 买卖股票的最佳时机

力扣 123. 买卖股票的最佳时机 III

力扣 309. 买卖股票的最佳时机含冷冻期

力扣 714. 买卖股票的最佳时机含手续费

三,其他非对称DP

CSU 1207 Strictly-increasing sequence

HDU 1176 免费馅饼

POJ 1390 Blocks


一,对称DP、非对称DP

这是我根据自己对DP的理解和归纳,提出来的新概念。

1,对称DP

数列DP 数列DP_nameofcsdn的博客-CSDN博客

区间DP https://blog.csdn.net/nameofcsdn/article/details/112981922

这2类DP其实都比较简单,因为对象空间等于解空间 解空间_nameofcsdn的博客-CSDN博客_解空间

如果问题要求的是f(i,j),那么算法中的递推式就会是f(i,j)=......

因为这里的i和j是同一个概念,地位对称,也因为对象空间和解空间的地位对称,所以我称之为对称DP

2,非对称DP

有一些DP问题,比如问题抽象成f(n),f的递推式很复杂甚至有可能根本不存在,

但是我们可以引入一个辅助概念,在对应的辅助空间内取一个变量i,得到子问题g(n,i)

根据g(n,i)的递推式算出其值,最后根据f(n)=h({g(n,i)|i∈A})算出f(n)

其中h表示的是如何根据子问题的解整合得到原问题的解,一般就是sum函数或者max函数或者min函数,而A表示的就是辅助空间

因为n所在的对象空间和辅助空间A共同构成解空间,即对象空间和解空间不同,也因为n和i不是同一个概念,所以我称之为非对称DP

二,状态转换DP

POJ 3661 Running

题目:

Description

The cows are trying to become better athletes, so Bessie is running on a track for exactly N (1 ≤ N ≤ 10,000) minutes. During each minute, she can choose to either run or rest for the whole minute.

The ultimate distance Bessie runs, though, depends on her 'exhaustion factor', which starts at 0. When she chooses to run in minute i, she will run exactly a distance of Di (1 ≤ Di ≤ 1,000) and her exhaustion factor will increase by 1 -- but must never be allowed to exceed M (1 ≤ M ≤ 500). If she chooses to rest, her exhaustion factor will decrease by 1 for each minute she rests. She cannot commence running again until her exhaustion factor reaches 0. At that point, she can choose to run or rest.

At the end of the N minute workout, Bessie's exaustion factor must be exactly 0, or she will not have enough energy left for the rest of the day.

Find the maximal distance Bessie can run.

Input

* Line 1: Two space-separated integers: N and M
* Lines 2..N+1: Line i+1 contains the single integer: Di

Output

* Line 1: A single integer representing the largest distance Bessie can run while satisfying the conditions.

Sample Input

5 2
5
3
4
2
10

Sample Output

9

题意:

在每一分钟内,可以选择跑步或休息一整分钟。在第i分钟跑步时,她将刚好跑完Di的距离。

跑步1分钟,疲惫因子将增加1——但决不能超过M,如果她选择休息,每休息一分钟,她的疲惫因子就会减少1。直到她的疲惫因子达到0,她才能重新开始跑步。

最终疲惫因子必须刚好是0,问最多可以跑多远。 

思路:

这个题目,我从头到尾产生过4种思路,前面3种都挂了,最后一种终于成功了。

思路一,超内存+超时,不能AC但是代码是对的,只不过效率很低。

思路二,无法满足题目的限制条件,代码是错的。

思路三,超时,代码是对的,但是效率还不够高。

思路四,完全正确。

思路一:把时间分成很多段,每一段都从exaustion factor为0开始,又以exaustion factor为0结束,求最多能跑多远。

sum[i][j]表示的就是从i到j的时间内,最多能跑多远,要满足上述条件。

代码:

#include<iostream>
#include<string.h>
using namespace std;
 
int list[10001];
int sum[10001][10001];
int a;
int n, m;
 
int f(int i, int j)		
{
	if (sum[i][j] >= 0)return sum[i][j];
	if (i >= j)return 0;
	if (i + 1 == j)return list[i];
	if (j - i + 1 <= m * 2 && (j - i) % 2)
	{
		sum[i][j] = 0;
		for (int k = i; k <= (i + j) / 2; k++)sum[i][j] += list[k];
	}
	for (int k = i; k < j; k++)
	{
		a = f(i, k) + f(k + 1, j);
		if (sum[i][j] < a)sum[i][j] = a;
	}
	return sum[i][j];
}
 
int main()
{
	cin >> n >> m;
	for (int i = 1; i <= n; i++)cin >> list[i];
	memset(sum, -1, sizeof(sum));
	cout << f(1, n);
	return 0;
}

后来提交了才发现,1万乘1万的数组太大了,超内存了。

其实不仅仅是内存的问题,这个思路的时间复杂度很高,不可取。

思路二:求到第 i 分钟结束,如果exaustion factor为 j 的话最多可以跑多远。

sum[i][j]表示的是对于任意i不超过n,任意j不超过m,第 i 分钟结束疲劳系数为j的情况下最多可以跑多远。

为什么我要强调任意,有深意,请往下看(在思路三的第4行)

只有对自己定义的状态sum数组十分的清楚,没有一点含糊,才不容易出错。

代码:

#include<iostream>
#include<string.h>
using namespace std;
 
int list[10001];
int sum[10001][501];
int a,b;
int n, m;
 
int f(int i, int j)		
{
	if (sum[i][j] >= 0)return sum[i][j];
	if (i == 1)
	{
		if (j)return list[1];
		return 0;
	}
	if (j > 0)sum[i][j] = f(i - 1, j - 1) + list[i];
	if (j < m)
	{
		a = f(i - 1, j + 1);
		if (sum[i][j] < a)sum[i][j] = a;
		if (j == 0)
		{
			a = f(i - 1, 0);
			if (sum[i][j] < a)sum[i][j] = a;
		}
	}
	return sum[i][j];
}
 
int main()
{
	cin >> n >> m;
	for (int i = 1; i <= n; i++)cin >> list[i];
	memset(sum, -1, sizeof(sum));
	cout << f(n, 0);
	return 0;
}

这里有个细节我不是很确定,就是sum[1][j]里面,如果j大于1到底有没有意义。

我觉得虽然sum[1][j]里面的j大于1是不现实的,但是应该对我们求解本问题的最优解是没有影响的,不过我不是很确定。

这个代码没有保证题目的限制条件:一旦开始休息,必须要休息到exaustion factor为0才能继续跑,果断放弃。

思路三:sum[i][j]表示的是到第 i 分钟结束,如果exaustion factor为 j 的话最多可以跑多远,

但是有个限制条件,如果 j 不是0的话,它表示的是exaustion factor关于时间的函数的极大值点。

也就是说,f(6,3)表示在第4、5、6分钟都在跑,而且跑完之后exaustion factor为0的情况下,最多可以跑多远。

这样的状态就很有意思了,不是对于跑的过程中可能产生的任意状态都定义sum,

而只对exaustion factor关于时间变化的函数的极大值处和零点处才定义sum。

请注意,不是不计算某些sum[i][j],而是不定义!前者是计算方法的略微区别,后者是思想的本质区别!

代码:

#include<iostream>
#include<string.h>
using namespace std;
 
int list[10001];
int sum[10001][501];
int a;
int n, m;
 
int f(int i, int j)
{
	if (i <= 0)return 0;
	if (sum[i][j] >= 0)return sum[i][j];	
	if (j == 0)
	{
		sum[i][j] = f(i - 1, 0);		//第i分钟在休息
		for (int k = i-m; k < i; k++)
		{
			a = f(k, i - k);
			//第k分钟在跑,到了顶点,然后开始休息,直到第i分钟结束才让疲劳度降为0
			if (sum[i][j] < a)sum[i][j] = a;
		}
	}
	else
	{
		a = 0;		//j不为0那么就是疲劳度的极大值点
		int ki = i;	//不需要进行任何的分类讨论,也不需要max函数之类的
		for (int kj = j; ki>0 && kj>0; ki--,kj--)a += list[ki];
		sum[i][j] = a + f(ki, 0);
	}
	return sum[i][j];
}
 
int main()
{
	cin >> n >> m;
	for (int i = 1; i <= n; i++)cin >> list[i];
	memset(sum, -1, sizeof(sum));
	cout << f(n, 0);
	return 0;
}

这个代码,还是超时了。

仔细一想,主要是f函数里面,当j不为0时有一个循环。

虽然不需要任何讨论,那个循环看起来也很短,但是在f不停的调用的情况下,这个循环的工作是很大的重复了的。

比如说,跑步方案一,1-20分钟在跑步,21-40分钟休息,

跑步方案二,1-19分钟在跑步,20-40分钟在休息。

显然方案一优于方案二,我们人在思考这个对比的时候其实是发现了这2个方案具有很高的相似性的。

对于比较任意的2个方案,我们就无法产生这种感觉了。

看上面的代码,计算sum[20][20]和计算sum[19][19],我们发现了什么?

天哪!几乎所有的工作(即加法运算)都是重复的!

意识到了问题的关键所在,我们离答案就不远了。

思路四:sum[i][j]表示的是到第 i 分钟结束,如果exaustion factor为 j 的话最多可以跑多远,

但是有个限制条件,如果 j 不是0的话,它表示的是第i分钟在跑的情况下最多可以跑多远。

对比思路三,我们可以发现,思路三只定义疲劳度函数的极大值点和零点处的sum,非常稀疏,

而思路四定义那些j为0或者第i分钟在跑的那些sum。

(顺便一提,这样定义的sum实际上是刚好定义了一半的,因为跑1分钟疲劳度加1,休息1分钟疲劳度减1

疲劳度函数由很多左右对称的凸峰组成。虽然凸峰之间的距离可以很大,但是在动态规划的算法中,

因为要求最优解,实际上凸峰之间都是距离不超过1的,不然的话中间又可以跑1分钟休息1分钟,形成1个小峰)

当然,这不是什么重要的发现,重要的是,思路三里面提到的重复计算,在思路四里面可以解决了!

代码:

#include<iostream>
#include<string.h>
using namespace std;
 
int list[10001];
int sum[10001][501];
int a;
int n, m;
 
int f(int i, int j)
{
	if (i <= 0)return 0;
	if (sum[i][j] >= 0)return sum[i][j];	
	if (j == 0)
	{
		sum[i][j] = f(i - 1, 0);
		for (int k = i-m; k < i; k++)
		{
			a = f(k, i - k);
			if (sum[i][j] < a)sum[i][j] = a;
		}
	}
	else sum[i][j] = f(i - 1, j - 1) + list[i];
	return sum[i][j];
}
 
int main()
{
	cin >> n >> m;
	for (int i = 1; i <= n; i++)cin >> list[i];
	memset(sum, -1, sizeof(sum));
	cout << f(n, 0);
	return 0;
}

思路四的代码,和思路三几乎差不多,唯一的区别就是,f函数里面,当j不为0的时候,实际上只要一条简单的语句就可以算出sum[i][j]

其他的地方都不需要修改。

如果仔细推敲的话,我们可以发现,思路三和思路四都能处理思路二的漏洞——满足题目的限制,休息的时候必须休息到疲劳度为0才能继续跑。

但是方式是有不小的区别的。

思路三是在求顶点(j不为0)处的sum的时候,直接用求和式往前推到开始跑(疲劳度为0)的时候。

而思路四却是在求j为0处的sum的时候,直接往前推到开始休息的时候。

虽然2个思路的代码在求j为0处的sum的时候是一模一样的,但是因为思路不同,思想不同,所以选择用来满足该限制的方式不同,才导致效率不一样。

差不多是我能AC的最难DP问题了。

力扣 188. 买卖股票的最佳时机 IV

给你一个整数数组 prices 和一个整数 k ,其中 prices[i] 是某支给定的股票在第 i 天的价格。

设计一个算法来计算你所能获取的最大利润。你最多可以完成 k 笔交易。也就是说,你最多可以买 k 次,卖 k 次。

注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

示例 1:

输入:k = 2, prices = [2,4,1]
输出:2
解释:在第 1 天 (股票价格 = 2) 的时候买入,在第 2 天 (股票价格 = 4) 的时候卖出,这笔交易所能获得利润 = 4-2 = 2 。

示例 2:

输入:k = 2, prices = [3,2,6,5,0,3]
输出:7
解释:在第 2 天 (股票价格 = 2) 的时候买入,在第 3 天 (股票价格 = 6) 的时候卖出, 这笔交易所能获得利润 = 6-2 = 4 。
     随后,在第 5 天 (股票价格 = 0) 的时候买入,在第 6 天 (股票价格 = 3) 的时候卖出, 这笔交易所能获得利润 = 3-0 = 3 。

提示:

  • 1 <= k <= 100
  • 1 <= prices.length <= 1000
  • 0 <= prices[i] <= 1000
class Solution {
public:
	int maxProfit(int k, vector<int>& prices) {
		vector<int>ans(prices.size());
		int maxAns = 0;
		for (int i = 0; i < k * 2; i++) {
			if (i % 2) {
				int m = INT_MIN;
				for (int j = 0; j < prices.size(); j++) {
					m = max(m, ans[j]);
					ans[j] = m + prices[j];
					maxAns = max(maxAns, ans[j]);
				}
			}
			else {
				int m = 0;
				for (int j = 0; j < prices.size(); j++) {
					int t = ans[j];
					ans[j] = m - prices[j];
					m = max(m, t);
				}
			}
		}
		return maxAns;
	}
};

力扣 121. 买卖股票的最佳时机

给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。

你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。

返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。

示例 1:

输入:[7,1,5,3,6,4]
输出:5
解释:在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
     注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。

示例 2:

输入:prices = [7,6,4,3,1]
输出:0
解释:在这种情况下, 没有交易完成, 所以最大利润为 0。

提示:

  • 1 <= prices.length <= 105
  • 0 <= prices[i] <= 104

直接用188的代码

class Solution {
public:
	int maxProfit(int k, vector<int>& prices) {
......
	}
	int maxProfit(vector<int>& prices) {
		return maxProfit(1, prices);
	}
};

力扣 123. 买卖股票的最佳时机 III

给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。

设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔 交易。

注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

示例 1:

输入:prices = [3,3,5,0,0,3,1,4]
输出:6
解释:在第 4 天(股票价格 = 0)的时候买入,在第 6 天(股票价格 = 3)的时候卖出,这笔交易所能获得利润 = 3-0 = 3 。
     随后,在第 7 天(股票价格 = 1)的时候买入,在第 8 天 (股票价格 = 4)的时候卖出,这笔交易所能获得利润 = 4-1 = 3 。

示例 2:

输入:prices = [1,2,3,4,5]
输出:4
解释:在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。   
     注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。   
     因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。

示例 3:

输入:prices = [7,6,4,3,1] 
输出:0 
解释:在这个情况下, 没有交易完成, 所以最大利润为 0。

示例 4:

输入:prices = [1]
输出:0

提示:

  • 1 <= prices.length <= 105
  • 0 <= prices[i] <= 105

直接用188的代码

class Solution {
public:
	int maxProfit(int k, vector<int>& prices) {
......
	}
	int maxProfit(vector<int>& prices) {
		return maxProfit(2, prices);
	}
};

力扣 309. 买卖股票的最佳时机含冷冻期

给定一个整数数组prices,其中第  prices[i] 表示第 i 天的股票价格 。​

设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):

  • 卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。

注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

示例 1:

输入: prices = [1,2,3,0,2]
输出: 3 
解释: 对应的交易状态为: [买入, 卖出, 冷冻期, 买入, 卖出]

示例 2:

输入: prices = [1]
输出: 0

提示:

  • 1 <= prices.length <= 5000
  • 0 <= prices[i] <= 1000
class Solution {
public:
	int maxProfit(vector<int>& prices) {
		int ans = 0;
		map<int, int>m0, m1;
		m1[-1] = -prices[0];
		for (int i = 0; i < prices.size(); i++) {
			m0[i] = max(m1[i - 1] + prices[i], m0[i-1]);
			m1[i] = max(m0[i - 2] - prices[i],m1[i-1]);
		}
		return m0[prices.size() - 1];
	}
};

力扣 714. 买卖股票的最佳时机含手续费

给定一个整数数组 prices,其中 prices[i]表示第 i 天的股票价格 ;整数 fee 代表了交易股票的手续费用。

你可以无限次地完成交易,但是你每笔交易都需要付手续费。如果你已经购买了一个股票,在卖出它之前你就不能再继续购买股票了。

返回获得利润的最大值。

注意:这里的一笔交易指买入持有并卖出股票的整个过程,每笔交易你只需要为支付一次手续费。

示例 1:

输入:prices = [1, 3, 2, 8, 4, 9], fee = 2
输出:8
解释:能够达到的最大利润:  
在此处买入 prices[0] = 1
在此处卖出 prices[3] = 8
在此处买入 prices[4] = 4
在此处卖出 prices[5] = 9
总利润: ((8 - 1) - 2) + ((9 - 4) - 2) = 8

示例 2:

输入:prices = [1,3,7,5,10,3], fee = 3
输出:6

提示:

  • 1 <= prices.length <= 5 * 104
  • 1 <= prices[i] < 5 * 104
  • 0 <= fee < 5 * 104
class Solution {
public:
	int maxProfit(vector<int>& prices, int fee) {
		int ans = 0;
		map<int, int>m0, m1;
		m1[-1] = -prices[0];
		for (int i = 0; i < prices.size(); i++) {
			m0[i] = max(m1[i - 1] + prices[i]- fee, m0[i-1]);
			m1[i] = max(m0[i - 1] - prices[i],m1[i-1]);
		}
		return m0[prices.size() - 1];
	}
};

三,其他非对称DP

CSU 1207 Strictly-increasing sequence

题目:

Description

如果一个序列中任意一项都大于前一项,那么我们就称这个序列为严格递增序列。

现在有一个整数序列,你可以将序列中任意相邻的若干项合并成一项,合并之后这项的值为合并前各项的值之和。通过若干次合并,最终一定能得到一个严格递增序列,那么得到的严格递增序列最多能有多少项呢?

Input

输入数据的第一行包含正整数T (1 <= T <= 200),表示接下来一共有T组测试数据。

每组测试数据的第一行包含一个整数N (1 <= N <= 1000),表示这个整数序列一共有N项。接下来一行包含N个不大于10^6的正整数,依次描述了这个序列中各项的值。

至多有20组数据满足N > 100。

Output

对于每组测试数据,用一行输出一个整数,表示最终得到的严格递增序列最多能有多少项。

Sample Input

3
2
1 1
3
1 2 3
5
1 3 2 6 7

Sample Output

1
3
4

思路:区间DP

dp[i][j]表示从1到j这j个数,最后一段为i到j的情况下,最优解的值(可能为0)

代码:

#include<iostream>
using namespace std;
 
int dp[1001][1001];//dp[i][j]表示最后一段为i到j的最优解
 
int main()
{
	int T, n, num[1001], sum[1001];
	cin >> T;
	while (T--)
	{
		cin >> n;
		sum[0] = 0;
		for (int i = 1; i <= n; i++)
		{
			cin >> num[i];
			dp[1][i] = 1, dp[i][i - 1] = 0;
			sum[i] = sum[i - 1] + num[i];
		}
		for (int i = 1; i < n; i++)
		{
			int ki = i;
			for (int j = i + 1; j <= n; j++)
			{
				dp[i + 1][j] = dp[i + 1][j - 1];
				while (ki)
				{
					if (sum[i] - sum[ki-1] >= sum[j] - sum[i])break;
					if (dp[ki][i] > 0 && dp[i + 1][j] < dp[ki][i] + 1)
						dp[i + 1][j] = dp[ki][i] + 1;
					ki--;
				}
			}
		}
		int ans = 0;
		for (int i = 1; i <= n; i++)if (ans < dp[i][n])ans = dp[i][n];
		cout << ans << endl;
	}
	return 0;
}

HDU 1176 免费馅饼

题目:

Description

都说天上不会掉馅饼,但有一天gameboy正走在回家的小径上,忽然天上掉下大把大把的馅饼。说来gameboy的人品实在是太好了,这馅饼别处都不掉,就掉落在他身旁的10米范围内。馅饼如果掉在了地上当然就不能吃了,所以gameboy马上卸下身上的背包去接。但由于小径两侧都不能站人,所以他只能在小径上接。由于gameboy平时老呆在房间里玩游戏,虽然在游戏中是个身手敏捷的高手,但在现实中运动神经特别迟钝,每秒种只有在移动不超过一米的范围内接住坠落的馅饼。现在给这条小径如图标上坐标: 
为了使问题简化,假设在接下来的一段时间里,馅饼都掉落在0-10这11个位置。开始时gameboy站在5这个位置,因此在第一秒,他只能接到4,5,6这三个位置中其中一个位置上的馅饼。问gameboy最多可能接到多少个馅饼?(假设他的背包可以容纳无穷多个馅饼) 

Input

输入数据有多组。每组数据的第一行为以正整数n(0<n<100000),表示有n个馅饼掉在这条小径上。在结下来的n行中,每行有两个整数x,T(0<T<100000),表示在第T秒有一个馅饼掉在x点上。同一秒钟在同一点上可能掉下多个馅饼。n=0时输入结束。 

Output

每一组输入数据对应一行输出。输出一个整数m,表示gameboy最多可能接到m个馅饼。 
提示:本题的输入数据量比较大,建议用scanf读入,用cin可能会超时。 

Sample Input

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

Sample Output

4

这个题目很明显就是动态规划了,可以理解为11个数组的DP问题,相当于数学归纳法里面的螺旋归纳法的加强版。

代码:

#include<iostream>
#include<stdio.h>
using namespace std;
 
 
int n, x, t;
int num[100001][11];
 
int get(int i, int j)
{
	int r = num[i - 1][j];
	if (j && r < num[i - 1][j - 1])r = num[i - 1][j - 1];
	if (j < 10 && r < num[i - 1][j + 1])r = num[i - 1][j + 1];
	return r;
}
 
int main()
{
	while (scanf("%d", &n))
	{
		if (n == 0)break;
		for (int j = 0; j <= 100000; j++)
			for (int i = 0; i < 11; i++)num[j][i] = 0;
		for (int i = 1; i <= n; i++)
		{
			scanf("%d%d", &x, &t);
			if (x >= 5 - t && x <= 5 + t)num[t][x] ++;
		}
		for (int j = 1; j <= 100000; j++)
			for (int i = 0; i < 11; i++)num[j][i] += get(j, i);		
		int maxx = 0;
		for (int i = 0; i < 11; i++)
			if (maxx < num[100000][i])maxx = num[100000][i];
		printf("%d\n", maxx);
	}
	return 0;
}

不管输入的是什么样的,程序是不需要排序的。

这个代码还可以继续优化时间,好多地方不需要用到100000,看输入的数据决定需要弄多大。

不过这个也就92ms就AC了,懒得再改了。

有一个地方需要注意,如果输入的n个馅饼中,其中1个是3,1,那这个馅饼是绝对接不到的。

这种情况需要区分开来,处理方法就是直接忽略不计数就好了。

判定条件是x >= 5 - t && x <= 5 + t,满足这个的都是可以接到的馅饼,不满足的都是接不到的。

POJ 1390 Blocks

题目:

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: 
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: 
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

代码:

#include<iostream>
using namespace std;
 
int num[201], col[201], ans[201][201][201];//前面附加t个同色盒子
 
int dp(int l, int r,int t)
{
	if (l > r)return 0;
	if (l == r)return (num[l] + t) * (num[l] + t);
	if (ans[l][r][t])return ans[l][r][t];
	int a = dp(l, l, t) + dp(l + 1, r, 0);
	for (int i = l + 1; i <= r; i++)
	{
		if (col[i] != col[l])continue;
		if (a < dp(l + 1, i - 1, 0) + dp(i, r, t + num[l]))
			a = dp(l + 1, i - 1, 0) + dp(i, r, t + num[l]);
	}
	ans[l][r][t] = a;
	return a;
}
int main()
{
	int T, n;
	cin >> T;
	for(int i=1;i<=T;i++)
	{
		cin >> n;
		int c1 = -1, c2, key = 0;
		for (int i = 0; i <= n; i++)
		{
			num[i] = 0;
			for (int j = 0; j <= n; j++)for(int t=0;t<=n;t++)ans[i][j][t] = 0;
		}
		for (int i = 0; i < n; i++)
		{
			cin >> c2;
			if (c1 != c2)c1 = c2, col[++key] = c2;
			num[key]++;
		}
		cout << "Case " << i << ": " << dp(1, key, 0) << endl;
	}
	return 0;
}

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值