洛谷题单指南-动态规划3-P4342 [IOI1998] Polygon
原题链接:https://www.luogu.com.cn/problem/P4342
题意解读:环中节点表示数字,边表示运算符,可以任意断一条边,其余节点两两按边的符号计算,求结果的最大值,以及最大值是断开那些边可以得到。
解题思路:
题意中有几个个关键信息:
- 环形,节点数为n,边数为n
- 任意断一条边,即可以从任意节点开始,进行区间长度为n个节点的合并操作
本质上就是一个环形dp问题,因此可以将原节点、边拉平并放大两倍长度,再枚举每一个长度1 ~ n的区间进行dp操作
1、状态表示
设s[i]为第i个符号,设a[i]为第i个数字, a[i]和a[i+1]通过符号s[i+1]相连
设f[i][j]表示从i ~ j之间可求出的最高分数
设g[i][j]表示从i ~ j之间可求出的最低分数
2、状态转移
设最后一次合并分界点在k,也就是i~k已合并,k+1~j也已合并,要计算f[i][j]
如果s[k+1]是加号t:
f[i][j] = max(f[i][j], f[i][k] + f[k+1][j]);
g[i][j] =min(g[i][j], g[i][k] +g[k+1][j]);
如果s[k+1]是乘号x:
由于有正负的差异,所以i~j最大值的计算有四种可能:
i~k的最大值 * k+1~j的最大值、i~k的最大值 * k+1~j的最小值、i~k的最小值 * k+1~j的最大值、i~k的最小值 * k+1~j的最小值
以上四种取max即可
f[i][j] = max(f[i][j], f[i][k] * f[k+1][j])
f[i][j] = max(f[i][j], f[i][k] *g[k+1][j])
f[i][j] = max(f[i][j], g[i][k] *f[k+1][j])
f[i][j] = max(f[i][j], g[i][k] *g[k+1][j])
同样的道理,i~j最小值的计算也有四种可能,直接给出递推式:
g[i][j] = min(g[i][j], g[i][k] * g[k+1][j])
g[i][j] = min(g[i][j], g[i][k] *f[k+1][j])
g[i][j] = min(g[i][j], f[i][k] *g[k+1][j])
3、初始化
f初始化为极小值,g初始化为极大值,对于len=1的区间f[i][i] = g[i][i] = a[i]
4、结果
所有f[i][i+n-1]的最大值
要看具体断开那条边,即为满足f[i][i+n-1]等于最大值是的i值。
100分代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 105; //放大两倍,环形dp问题
const int INF = 0x3f3f3f3f;
int n;
char s[N]; //符号
int a[N]; //数字,a[i]和a[i+1]通过符号s[i+1]相连
int f[N][N]; //f[i][j]表示从i ~ j之间可求出的最高分数
int g[N][N]; //g[i][j]表示从i ~ j之间可求出的最低分数
int ans = -INF; //要计算最大值,有负数存在,因此初始化为极小值
int main()
{
cin >> n;
for(int i = 1; i <= n; i++)
{
cin >> s[i] >> a[i];
s[i + n] = s[i]; //两倍长度
a[i + n] = a[i]; //两倍长度
}
for(int i = 1; i <= 2 * n; i++)
{
for(int j = 1; j <= 2 * n; j++)
{
f[i][j] = -INF;
g[i][j] = INF;
}
}
for(int len = 1; len <= n; len++) //枚举区间长度
{
for(int i = 1; i + len - 1 <= 2 * n; i++) //枚举左端点
{
int j = i + len - 1; //计算右端点
if(len == 1) f[i][j] = g[i][j] = a[i]; //区间长度为1,合并结果是自身数值
else
{
for(int k = i; k < j; k++) //枚举最后一次合并的分界点,i~k、k+1~j都已合并完
{
if(s[k+1] == 't')
{
f[i][j] = max(f[i][j], f[i][k] + f[k+1][j]);
g[i][j] = min(g[i][j], g[i][k] + g[k+1][j]);
}
if(s[k+1] == 'x')
{
f[i][j] = max(f[i][j], f[i][k] * f[k+1][j]);
f[i][j] = max(f[i][j], f[i][k] * g[k+1][j]);
f[i][j] = max(f[i][j], g[i][k] * f[k+1][j]);
f[i][j] = max(f[i][j], g[i][k] * g[k+1][j]);
g[i][j] = min(g[i][j], g[i][k] * g[k+1][j]);
g[i][j] = min(g[i][j], g[i][k] * f[k+1][j]);
g[i][j] = min(g[i][j], f[i][k] * g[k+1][j]);
g[i][j] = min(g[i][j], f[i][k] * f[k+1][j]);
}
}
}
}
}
for(int i = 1; i <= n; i++) ans = max(ans, f[i][i+n-1]);
cout << ans << endl;
for(int i = 1; i <= n; i++)
{
if(f[i][i+n-1] == ans)
{
cout << i << " "; //断开边的编号即同起始点的位置
}
}
return 0;
}