问题描述
思路
刚拿到题目的时候感觉是一脸懵的,道理我都懂,但是思路和头绪倒是一直没有蹦出来,妥妥的一个模拟类题的茫然脸。
第一个思路肯定是暴力穷举,当然这种办法比较low,而且很可能在时空复杂度上爆掉,所以直接扔掉。
首先尝试手动模拟流程,寻找规律,主要从 + * 的顺序入手,观察数字对它们的影响。由于负数的存在,分类讨论难度过于繁复,先放一边,其他思路不可以再用它推进。
因为是个最优解问题,很自然地想到局部最优推得全局最优,因此萌生了动态规划计算最优子结构的办法。
动态规划
那么问题来了,既然是动态规划计算最优子结构,那怎样算最优子结构呢?不同于其他问题直接取最大值为最优子结构,因为这个问题存在乘法负负得正的情况,因此我们还需要考虑取添加分类讨论过程,将乘法时左右两边都为负列入考虑范围。
考虑了最优子结构的定义,剩下的就是递归求解的过程了。我们知道动态规划中有两个非常重要的概念——状态转移方程和临界条件。我们从合并的角度考虑问题:
+:
min_now = minl + minr
max_now = maxl + maxr
*:
min_now = min(minl * minr, maxl * maxr, minl * maxr, minr * maxl)
max_now = max(minl * minr, maxl * maxr, minl * maxr, minr * maxl)
式中,minl,maxl为合并前符号左边的最小值与最大值;minr,maxr为合并前符号右边的最小值与最大值。因为考虑到正负号的问题,所以*部分需要对四种情况讨论。
个人认为这个思路还是穷举的味道比较重的。
代码
#include<bits/stdc++.h>
using namespace std;
int get_minmax(int left, int point, int len);
int n;
// 用了递归,数据不可能太大的,因为缓冲区会炸
// max[i][j]从第i位开始,往后数j个数(包括第i个数),j是链子的长度
int max_num[1000][1000], min_num[1000][1000];
char op[1000];
int get_minmax(int left, int point, int len){
int min_now, max_now; // 断在s时合起来的最大最小
int real_point = (left+point)%n; // 因为是个圆,这意味这断real_point边
int minleft = min_num[left][point]; // 因为point<len,所以这里的区间都是被计算过的
int maxleft = max_num[left][point];
int minright = min_num[real_point][len-point];
int maxright = max_num[real_point][len-point];
// 计算s点合并掉后新的点的最大值最小值
if(op[real_point] == '+'){
min_now = minleft+minright;
max_now = maxleft+maxright;
}else{
min_now = min(minleft*minright, min(minleft*maxright, min(maxleft*minright, maxleft*maxright)));
max_now = max(minleft*minright, max(minleft*maxright, max(maxleft*minright, maxleft*maxright)));
}
return min_now, max_now;
}
int main(){
scanf("%d", &n);
char jump;
scanf("%c", &jump);
for (int i = 0; i<n; i++){
scanf("%c", &op[i]);
int temp;
scanf("%d", &temp);
max_num[i][1] = temp; // 初始化
min_num[i][1] = temp;
}
for (int i = 0;i<n;i++){
printf("%d ",max_num[i][1]);
}
printf("\n");
int min_now,max_now; // 代表[i,j]中能出的最大值最小值
// 第一重对应不同长度下两边的最优解。因此到了n就是最终结果
for(int j=2; j<=n; j++) // 因为j=1已经在之前确定完了,长度为1的时候就是自己。
// 第二重对应不同起点下不同长度的最优解,就是区间[i,i+j-1]的里的最优解
for(int i=0;i<n;i++) // 要干掉第i个点
// 第三重就是在这个区间中找到断点的最佳位置
for(int s=1; s<=j-1; s++){ // s是对应[i,i+j]中间的第s个数
min_now, max_now = get_minmax(i, s, j);
max_num[i][j] = max(max_num[i][j], max_now);
min_num[i][j] = min(min_num[i][j], min_now);
}
int ret = max_num[0][n];
// for(int i = 0; i<n; i++){
// ret = max(ret, max_num[i][n]);
// for (int j=1;j<=n;j++)
// printf("%d ", max_num[i][j]);
// printf("\n");
// }
printf("%d", ret);
return 0;
}
//测试数据:
//4
//+-7+4*2*5
三重for循环中的前两重的顺序是个细节操作,不可以换位置,因为换位之后会造成计算时用到了没被处理的max_num和min_num,比如计算max_num[0][3]时,当point为1,则max_num=max_num[0][1]+max_num[1][2],此时因为大循环是首个空断边,所以才进行到0,max_num[1][2]还没被计算到,所以仍为0。
黄色背景的是被计算过的数据,红色字体的是正在计算的数据,绿色字体的是此时计算所要用到的数据。我们可以发现先循环长度才能保证每次被用到的数据的断点长度point<已有长度len,同时用到的数据max_num[real_point][len-point]也被计算完了。如下图: