Luogu4342 [IOI1998]Polygon(区间DP)题解
题意
合并一堆数,每次合并相加或相乘(题目已给出),设计合并顺序使得最终答案最大
其实就是一个区间DP
设\(f_{i, j}\)为合并区间\([i,j]\)的最大价值
加法很好写:\(f_{i, j} = max_{k \geq i}^{k < j}{(f_{i, k} + f_{k + 1, j})}\)
乘法也很好写:\(f_{i, j} = max_{k \geq i}^{k < j}{(f_{i, k} * f_{k + 1, j})}\)
然而
你会发现过不了样例……
??????
仔细想一想,事实上,因为存在负数,所以对于乘法操作,最大值完全可能是由两个极小的负数相乘得到的。因此,我们还需要DP一个最小值。
设\(g_{i, j}\)为合并区间\([i,j]\)的最小价值
那么乘法转移应该写成:
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\)的转移也是同理的。
完整代码
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
const int maxn = 110;
int n,f[maxn][maxn],opt[maxn],a[maxn],g[maxn][maxn];
char c[5];
int main(){
scanf("%d", &n);
for(int i = 1; i <= n; ++ i){
scanf("%s%d", c, a + i);
if(c[0] == 'x') opt[i] = opt[i + n] = 2;
else opt[i] = opt[i + n] = 1;
}
memset(f,-0x3f,sizeof(f));
memset(g,0x3f,sizeof(g));
for(int i = 1; i <= n; ++ i) a[i + n] = a[i];
n *= 2;
for(int i = 1; i <= n; ++ i) f[i][i] = g[i][i] = a[i];
for(int l = 1; l < n; ++ l)
for(int i = 1, j = i + l; i < n && j <= n; ++ i, j = i + l)
for(int k = i; k < j; ++ k){
if(opt[k + 1] == 1){
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]);
}
else{
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], f[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] * f[k + 1][j]);
}
}
int Ans = -0x3f3f3f3f;
n /= 2;
for(int i = 1; i <= n; ++ i) Ans = max(Ans, f[i][i + n - 1]);
printf("%d\n", Ans);
for(int i = 1; i <= n; ++ i)
if(f[i][i + n - 1] == Ans) printf("%d ", i);
return 0;
}