soj 11600 Pick It 矩阵连乘模型 动态规划 acdream 1208 qj的奇怪宝具 noip 2006 energy 能量项链

矩阵连乘的模型,矩阵连乘是一个经典的动态规划问题,先来简要介绍一些这个问题。设有n个矩阵A1,A2,A3,。。。,An,其中相邻的两个矩阵是可乘的,问题是n个矩阵的乘积A1×A2×。。。×An,因为矩阵具有结合率,所以这个算式可以有多个计算次序。如4个矩阵(A1(A2(A3A4 ))),(A1((A2A3 )A4)),((A1A2)(A3A4))等等,可以发现这三种乘法的运算量是不一样的,我们的问题就是如何计算能使n个矩阵乘法的运算量最小。矩阵i的维数是p[i-1]*p[i]。容易得出维数m*n和n*p的两个矩阵的运算量是m*n*p。

用动态规划来求,状态dp[i][j]记为Ai到Aj的最优计算数。则对于A1到An,总有最后一次乘积,不妨设这个位置是k,则可以表示为((A1A2…Ak)(Ak+1…An) ),k为1到n-1,它的计算量是dp[1][n]=dp[1][k]+dp[k+1][n]+p[0]*p[k]*p[n],它具有最优子结构性质,所以得到的解一定是最优的,对于dp[i][j],我们可以枚举最后一次计算的位置k,i<=k<j,我们就可以得到状态转移方程:dp[i][j]=min(dp[i][k] + dp[k+1][j] + p[i-1]*p[k]p[j]),i<=k<j。然后就可以求解了,可以使用记忆化搜索做,也可以递推,不过递推要以j-i的从小到达顺序来推,因为长区间的值要依赖短区间的值。

对于这个题目,我们把每拿掉一个整数当成一次矩阵乘法,不过这个量化的是分数p[i-1]+p[k]+[j],而且要求的是最大的,稍微修改就可以了。
代码如下:
/*************************************************************************
	> File Name: 11600.cpp
	> Author: gwq
	> Mail: gwq5210@qq.com 
	> Created Time: 2014年11月09日 星期日 12时31分54秒
 ************************************************************************/

#include <cmath>
#include <ctime>
#include <cctype>
#include <climits>
#include <cstdio>
#include <cstdlib>
#include <cstring>

#include <map>
#include <set>
#include <queue>
#include <stack>
#include <string>
#include <vector>
#include <sstream>
#include <iostream>
#include <algorithm>

#define INF (INT_MAX / 10)
#define clr(arr, val) memset(arr, val, sizeof(arr))
#define pb push_back
#define sz(a) ((int)(a).size())

using namespace std;
typedef set<int> si;
typedef vector<int> vi;
typedef map<int, int> mii;
typedef long long ll;

const double esp = 1e-5;

#define N 210

int dp[N][N], w[N], n;

/*
 * 矩阵连乘的模型
 * 记忆化搜索
 * dp数组要初始化为-1
 */
int getans(int l, int r)
{
	if (dp[l][r] >= 0) {
		return dp[l][r];
	} else if (l == r) {
		dp[l][r] = 0;
		return dp[l][r];
	} else {
		for (int k = l; k < r; ++k) {
			int tmp = getans(l, k) + getans(k + 1, r)
				+ w[l - 1] * w[k] * w[r];
			dp[l][r] = max(dp[l][r], tmp);
		}
		return dp[l][r];
	}
}

/*
 * 非记忆化搜索
 */
int getans2(void)
{
	//先算短区间,再算长区间,因为长区间的值依赖短区间的值
	clr(dp, 0);
	for (int i = 1; i < n; ++i) {
		for (int j = 1; j + i <= n; ++j) {
			for (int k = j; k < j + i; ++k) {
				int tmp = dp[j][k] + dp[k + 1][j + i]
					+ w[j - 1] + w[k] + w[j + i];
				dp[j][j + i] = max(dp[j][j + i], tmp);
			}
		}
	}

	return dp[1][n];
}

int main(int argc, char *argv[])
{
	while (scanf("%d", &n) != EOF) {
		if (n == 0) {
			break;
		}
		for (int i = 0; i < n; ++i) {
			scanf("%d", &w[i]);
		}
		clr(dp, -1);
		--n;
		//printf("%d\n", getans(1, n));
		printf("%d\n", getans2());
	}

	return 0;
}

/*
11600. Pick It

Constraints
Time Limit: 5 secs, Memory Limit: 256 MB

Description
You are playing a game of Pick It. You are given a list of positive
integers, and you are allowed to select any number other than the
first or last number in this list. When you pick a number, that number
is removed from the board, and your score increases by the sum of the
number that you picked and the sum of the neighbouring numbers.

For example, if the list contained 1 2 3 4 5, and you picked 3, your
score would be 2+3+4=9. On the next turn, your list would be 1 2 4 5,
and if you picked 4 next, your score would be 9+2+4+5 = 20, leaving
you with the list 1 2 5. The game concludes when there are only two
numbers remaining.

Given a list of numbers, what is the maximum score that you can obtain?

Input

The input will consist of a number of test cases (at most 200 test cases).
A test case is of the form n k1 k2 ... kn where n (n ≤ 200) is the number
of numbers in the list, and each integer ki satisfies 1 ≤ ki ≤ 100). In
all test cases n ≥ 3, except in the case where n=0, which indicates the
end of input.

Output
For each test case, output the maximum score attainable.

Sample Input
5 1 2 3 4 5
5 2 1 5 3 4
6 30 20 40 50 70 60
0

Sample Output
30
31
570

Problem Source
2014年每周一赛第八场
*/

acdream 1208 qj的奇怪宝具也是一个类似与矩阵连乘的题目,不过这个题目的“矩阵”是一个环状的,可以从任意一个位置开始计算,如果要枚举开始的位置的话,复杂度是O(n^4),题目给的范围是100,有点儿危险。另一种思路是模拟一个环,将矩阵复制一份放在后边,从每个位置开始往后数n个矩阵,这样就避免了枚举,同时也得到了所有的情况,答案就是这n种情况的最大者。
这道题目可能是评测有问题,写成多case的情况就WA,写成单case的情况就AC了。
代码如下:
/*************************************************************************
	> File Name: 1208.cpp
	> Author: gwq
	> Mail: gwq5210@qq.com 
	> Created Time: 2014年11月09日 星期日 13时20分31秒
 ************************************************************************/

#include <cmath>
#include <ctime>
#include <cctype>
#include <climits>
#include <cstdio>
#include <cstdlib>
#include <cstring>

#include <map>
#include <set>
#include <queue>
#include <stack>
#include <string>
#include <vector>
#include <sstream>
#include <iostream>
#include <algorithm>

#define INF (INT_MAX / 10)
#define clr(arr, val) memset(arr, val, sizeof(arr))
#define pb push_back
#define sz(a) ((int)(a).size())

using namespace std;
typedef set<int> si;
typedef vector<int> vi;
typedef map<int, int> mii;
typedef long long ll;

const double esp = 1e-5;

#define N 210

int dp[N][N], w[N], n, m;

/*
 * 也是矩阵连乘的模型
 */
int getans(int l, int r)
{
	if (dp[l][r] >= 0) {
		return dp[l][r];
	} else if (l == r) {
		dp[l][r] = 0;
		return dp[l][r];
	} else {
		for (int k = l; k < r; ++k) {
			int tmp = getans(l, k) + getans(k + 1, r)
				+ w[l - 1] * w[k] * w[r];
			dp[l][r] = max(dp[l][r], tmp);
		}
		return dp[l][r];
	}
}

/*
 * 递推,也是先推短区间,再推长区间
 */
void getans2(void)
{
	clr(dp, 0);
	for (int i = 1; i < m; ++i) {
		for (int j = 1; j + i <= m; ++j) {
			for (int k = j; k < j + i; ++k) {
				int tmp = dp[j][k] + dp[k + 1][j + i]
					+ w[j - 1] * w[k] * w[j + i];
				dp[j][j + i] = max(dp[j][j + i], tmp);
			}
		}
	}
}

/*
 * 不知道什么原因,这个题目在ACdream的oj上用多case交就会WA
 * 单case就可以AC,可能是oj的问题。
 * 将能量项链向后复制一份就可以模拟循环取珠子的情况了
 * 然后找到n个珠子取法中的最大值就行了,复杂度为O(n^3)
 * 另一种方法是枚举取的第一个位置,复杂度是O(n^4),应该会超时。
 */
int main(int argc, char *argv[])
{
	scanf("%d", &n);
	for (int i = 0; i < n; ++i) {
		scanf("%d", &w[i]);
		w[i + n] = w[i];
	}
	clr(dp, -1);
	m = 2 * n - 1;
	int ans = 0;
	getans2();
	for (int i = 1; i <= n; ++i) {
		//ans = max(getans(i, i + n - 1), ans);
		ans = max(dp[i][i + n - 1], ans);
	}
	printf("%d\n", ans);

	return 0;
}

/*
qj的奇怪宝具
Time Limit: 2000/1000MS (Java/Others)
Memory Limit: 128000/64000KB (Java/Others)
Problem Description
经历千辛万苦,qj终于找到了爱情,但是爱情却被一条怪物守护着,
因此他必须击败怪物才能得到爱情。

这时,qj掏出了自己的宝具,一条无敌的项链。只要一带上这条项链,
就能让qj获得巨大的能量。

为什么呢?

原来在这条项链上有N颗能量珠。能量珠是一颗有头标记与尾标记的珠子,这些标
记对应着某个正整数。并且,对于相邻的两颗珠子,前一颗珠子的尾标记一定等
于后一颗珠子的头标记。因为只有这样,通过qj祖传的融合魔法,这两颗珠子才
能聚合成一颗珠子,同时释放出可以被qj吸收的能量。

如果前一颗能量珠的头标记为m,尾标记为r,后一颗能量的头标记为r,
尾标记为n,则聚合后释放的能量为 m*r*n,新产生的珠子的头标记为m,尾标记为n。

显然易见,如果将所有项链上的珠子使用qj祖传的融合魔法,qj会吸收大量
能量,击败怪物。

不过令qj这个数学残废着急的是,不同的聚合顺序得到的总能量是不同的,
请你帮他设计一个聚合顺序,使一串项链释放出的总能量最大。

Input
第一行是一个正整数N(4≤N≤100),表示项链上珠子的个数。第二行是N个用空
格隔开的正整数,所有的数均不超过1000。第i个数为第i颗珠子的头标
记(1≤i≤N),当i<N时,第i颗珠子的尾标记应该等于第i+1颗珠子的头
标记。第N颗珠子的尾标记应该等于第1颗珠子的头标记。
至于珠子的顺序,你可以这样确定:将项链放到桌面上,不要出现交叉,
随意指定第一颗珠子,然后按顺时针方向确定其他珠子的顺序。

Output
输出只有一行,是一个正整数E(E≤2.1*10^9),为一个最优聚合顺序所释放的总能量。

Sample Input
4
2  3  5  10

Sample Output
710

Hint
例如:设N=4,4颗珠子的头标记与尾标记依次为(2,3) (3,5) (5,10) (10,2)。
我们用记号⊕表示两颗珠子的聚合操作,(j⊕k)表示第j,k两颗珠子聚合后所释放的能
量。则第4、1两颗珠子聚合后释放的能量为:
(4⊕ 1)=10*2*3=60。
这一串项链可以得到最优值的一个聚合顺序所释放的总能量为
((4⊕ 1)⊕ 2)⊕ 3 = 10*2*3+10*3*5+10*5*10=710。

Source
noip 2006
*/


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值