多边形题解

3 篇文章 0 订阅
题目

法1(直接按照题意实现)

分析

这种方法不是很难…吧。用一个循环枚举从第i个点切开,然后就是一个石子合并 搭配我的bolg食用更佳

注意

这道题由于有乘号,而负数 * 负数 = 整数,所以要保存一个最小值,还要保存一个最大值

代码高亮

可能是我写麻烦了,代码非常长

#include <queue>
#include <cstdio>
using namespace std; 

const int MAXN = 55;
const int INF = 0x3f3f3f3f;
int n, _max = -INF;
int a[MAXN], tema[MAXN];
bool s[MAXN], tems[MAXN];
//s[i]和tems[i] = 1表示这一位为加号(i与i - 1的连接的符号)
//tema, tems存的是"切开"后的链 
int f[MAXN][MAXN][2];
//f[i][j][0]存储的是i~j(tem数组)的最大值
//f[i][j][1]存储的是i~j(tem数组)的最小值 

void MakeSet(int);//将"切开"后的链给到tema 和 tems中 
int work();//石子合并求最大值 
int Min(int x, int y) {return x < y ? x : y;}
int Max(int x, int y) {return x > y ? x : y;}

queue<int> ans;

int main() {
	scanf("%d", &n);
	for(int i = 1; i <= n; i++) {
		char tem[3];
		scanf("%s", tem + 1);
		if(tem[1] == 't') {//加号
			s[i] = 1;
		}
		else {//乘号 
			s[i] = 0;
		}
		scanf("%d", &a[i]);
	}
	for(int i = 1; i <= n; i++) {//从i处为开头(从i处切开) 
		MakeSet(i);//初始化 
		int tem = work();//算出最大值 
		if(tem == _max)//与最大值相同 
			ans.push(i);//多一种方法 
		if(tem > _max) {//大于最大值 
			while(!ans.empty()) ans.pop();//清空 
			ans.push(i);//记录下这种方法 
			_max = tem;//更新最大值 
		}
	}
	printf("%d\n", _max);
	while(!ans.empty()) printf("%d ", ans.front()), ans.pop();
	return 0;
}

void MakeSet(int x) {//从x处切开 
	int sum = 0; 
	for(int i = x; i <= n; i++) {
		tema[++sum] = a[i];
		tems[sum] = s[i];
	}
	for(int i = 1; i <= x - 1; i++) {
		tema[++sum] = a[i];
		tems[sum] = s[i];
	}
}

int work() {//石子合并
	for(int i = 1; i <= n; i++) f[i][i][0] = tema[i], f[i][i][1] = tema[i];//初始化 
	for(int i = 1; i <= n; i++) {//枚举长度 
		for(int j = 1; j <= n - i; j++) {//枚举左端点 
			int l = j, r = i + j;
			f[l][r][0] = -INF;//求最大值 
			f[l][r][1] = INF;//求最小值 
			for(int k = l; k <= r - 1; k++) {//枚举断点(和后面一个合并) 
				if(tems[k + 1] == 1) {//加号 
					f[l][r][0] = Max(f[l][r][0], f[l][k][0] + f[k + 1][r][0]);
					f[l][r][1] = Min(f[l][r][1], f[l][k][1] + f[k + 1][r][1]);
				}
				else {//乘号 
					int ans1 = f[l][k][0] * f[k + 1][r][0]; 
					int ans2 = f[l][k][0] * f[k + 1][r][1];
					int ans3 = f[l][k][1] * f[k + 1][r][0];
					int ans4 = f[l][k][1] * f[k + 1][r][1];
					//四种组合方式 
					f[l][r][0] = Max(f[l][r][0], Max(ans1, Max(ans2, Max(ans3, ans4))));
					f[l][r][1] = Min(f[l][r][1], Min(ans1, Min(ans2, Min(ans3, ans4))));
				}
			}
		}
	}
	return f[1][n][0];
}

法2(2倍法)

我们举个例子

有五个数:a, b, c, d, e

从a前切开并排成一列后,Ta是这样的

从b前切开并排成一列后,Ta是这样的

而我们会发现一个很有意思的东东,在这两个链中,都出现了:

那么我们能不能以此为契机,降低时间复杂度呢?

答案是肯定的!!!

我们将第一个链(其实从任何一个点切开后组成的链都可以,只是这样更符合我们的习惯)复制一份

则会得到a - b - c - d - e - a - b - c - d - e,记作*链

从a前切开后得到的链就是*链的第1个到第5个

从b前切开后得到的链就是*链的第2个到第6个

那我们只用对链进行“石子合并”了(因为链可以等效的代替原来各种方法的结果),答案就是*链中相隔4个的点之间进行“石子合并”后的最大值。

代码

#include <queue>
#include <cstdio>
using namespace std;

const int MAXN = 55;
const int INF = 0x3f3f3f3f;
int n;
int a[MAXN * 2];
int f[MAXN * 2][MAXN * 2][2];
//f[i][j][0]表示i~j合并后的最大值
//f[i][j][1]表示i~j合并后的最小值 
bool s[MAXN * 2];
//s[i] = 0表示i与i-1的联结符号为+ 

queue<int> ans;

int Max(int x, int y) {return x > y ? x : y;}
int Min(int x, int y) {return x < y ? x : y;}

int main() {
	scanf("%d", &n);
	for(int i = 1; i <= n; i++) {
		char tem[2];
		scanf("%s", tem + 1);
		if(tem[1] == 't') s[i] = 0;//加号 
		else s[i] = 1;
		scanf("%d", &a[i]);
	}
	for(int i = 1; i <= n; i++) {
		a[i + n] = a[i];
		s[i + n] = s[i];
	}
	
//	for(int i = 1; i <= n * 2; i++) {
//		printf("%d ", a[i]);
//		if(i == n * 2) break;
//		if(s[i + 1] == 0) printf("+ ");
//		else printf("* ");
//	} 
//	printf("\n");
	
	for(int i = 1; i <= n * 2; i++) f[i][i][0] = f[i][i][1] = a[i];
	for(int i = 1; i <= n; i++) {//区间长度 
		for(int j = 1; j <= n * 2 - i; j++) {//枚举左端点 
			int l = j, r = j + i;
			f[l][r][0] = -INF;
			f[l][r][1] = INF;
			for(int k = l; k <= r - 1; k++) {//枚举断点 
				if(s[k + 1] == 0) {//加号 
					f[l][r][0] = Max(f[l][r][0], f[l][k][0] + f[k + 1][r][0]);
					f[l][r][1] = Min(f[l][r][1], f[l][k][1] + f[k + 1][r][1]);
				}
				else {//乘号 
					int ans1 = f[l][k][0] * f[k + 1][r][0];
					int ans2 = f[l][k][0] * f[k + 1][r][1];
					int ans3 = f[l][k][1] * f[k + 1][r][0];
					int ans4 = f[l][k][1] * f[k + 1][r][1];
					f[l][r][0] = Max(f[l][r][0], Max(ans1, Max(ans2, Max(ans3, ans4))));
					f[l][r][1] = Min(f[l][r][1], Min(ans1, Min(ans2, Min(ans3, ans4))));
				}
			}
		}
	}
//	for(int i = 1; i <= n * 2; i++) { 
//		for(int j = i; j <= n * 2; j++) {
//			printf("f[%d][%d][0] = %d\n", i, j, f[i][j][0]);
//			printf("f[%d][%d][1] = %d\n", i, j, f[i][j][1]);
//		}
//	} 
	int _max = -INF;
	for(int i = 1; i <= n; i++) {
		if(f[i][i + n - 1][0] == _max) ans.push(i);
		if(f[i][i + n - 1][0] > _max) {
			_max = f[i][i + n - 1][0];
			while(!ans.empty()) ans.pop();
			ans.push(i);
		}
//		printf("%d %d\n", i, f[i][i + n - 1][0]);
	}
	printf("%d\n", _max);
	while(!ans.empty()) printf("%d ", ans.front()), ans.pop();
	return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值