「IOI 1998」Polygon (区间DP题解)

题目翻译

“多边形游戏”是一款单人益智游戏。游戏开始时,给定玩家一个具有 N N N个顶点 N N N条边(编号 1 ∼ N 1 \sim N 1N)的多边形,如图1所示,其中 N = 4 N = 4 N=4
每个顶点上写有一个整数,每个边上标有一个运算符 + + +(加号)或运算符 × \times ×(乘号)。

第一步,玩家选择一条边,将它删除。
接下来在进行 N − 1 N-1 N1 步,在每一步中,玩家选择一条边,把这条边以及该边连接的两个顶点用一个新的顶点代替,新顶点上的整数值等于删去的两个顶点上的数按照删去的边上标有的符号进行计算得到的结果。
下面是用图1给出的四边形进行游戏的全过程。

最终,游戏仅剩一个顶点,顶点上的数值就是玩家的得分,上图玩家得分为 0 0 0
请计算对于给定的 N N N边形,玩家最高能获得多少分,以及第一步有哪些策略可以使玩家获得最高得分。

输入格式

输入包含两行,第一行为整数 N N N
第二行用来描述多边形所有边上的符号以及所有顶点上的整数,从编号为 1 1 1的边开始,边、点、边 … \dots 按顺序描述。
其中描述顶点即为输入顶点上的整数,描述边即为输入边上的符号,其中加号用“ t t t”表示,乘号用“ x x x”表示。

输出格式

输出包含两行,第一行输出最高分数。
在第二行,将满足得到最高分数的情况下,所有的可以在第一步删除的边的编号从小到大输出,数据之间用空格隔开。

样例

输入
4
t -7 t 4 x 2 x 5
输出
33
1 2

数据范围与提示

3 ≤ N ≤ 50 3 \leq N \leq 50 3N50。数据保证无论玩家如何操作,顶点上的数值均在 [ − 32768 , 32767 ] [-32768,32767] [32768,32767]之内。

分析

首先,读者很容易想到这是一道 区间DP(把两个相邻的元素合并成一个)。可是当我们用常规的 d p [ i ] [ j ] dp[i][j] dp[i][j]去定义左端点为 i i i,右端点为 j j j的区间合并成一个点的最大值会发现,此题的最大值不一定是两个最大值相乘或相加,还可以由最小值得来。
于是我们定义 d p [ i ] [ j ] [ 0 ] dp[i][j][0] dp[i][j][0]表示最大值, d p [ i ] [ j ] [ 1 ] dp[i][j][1] dp[i][j][1]表示最小值, k ( i ≤ k < j ) k(i \leq k < j) k(ik<j)
则有 d p [ i ] [ j ] [ 0 ] = m a x { d p [ i ] [ k ] [ 0 ] + d p [ k + 1 ] [ j ] [ 0 ] d p [ i ] [ k ] [ 0 ] × d p [ k + 1 ] [ j ] [ 0 ] d p [ i ] [ k ] [ 1 ] × d p [ k + 1 ] [ j ] [ 1 ] dp[i][j][0] = max \begin{cases} dp[i][k][0] +dp[k+1][j][0]\\dp[i][k][0] \times dp[k+1][j][0] \\ dp[i][k][1] \times dp[k+1][j][1]\end{cases} dp[i][j][0]=maxdp[i][k][0]+dp[k+1][j][0]dp[i][k][0]×dp[k+1][j][0]dp[i][k][1]×dp[k+1][j][1]
d p [ i ] [ j ] [ 1 ] = m a x { d p [ i ] [ k ] [ 1 ] + d p [ k + 1 ] [ j ] [ 1 ] d p [ i ] [ k ] [ 1 ] × d p [ k + 1 ] [ j ] [ 1 ] d p [ i ] [ k ] [ 1 ] × d p [ k + 1 ] [ j ] [ 0 ] d p [ i ] [ k ] [ 0 ] × d p [ k + 1 ] [ j ] [ 1 ] dp[i][j][1] = max \begin{cases} dp[i][k][1] +dp[k+1][j][1]\\dp[i][k][1] \times dp[k+1][j][1] \\ dp[i][k][1] \times dp[k+1][j][0] \\ dp[i][k][0] \times dp[k+1][j][1]\end{cases} dp[i][j][1]=maxdp[i][k][1]+dp[k+1][j][1]dp[i][k][1]×dp[k+1][j][1]dp[i][k][1]×dp[k+1][j][0]dp[i][k][0]×dp[k+1][j][1]如果我们不会环形DP的处理方法,需要暴力枚举第一次断的那条边,时间复杂度 O ( n 4 ) O(n^4) O(n4)
根据环形DP的处理方法,可以将长度为 n n n的环断成长度为 2 n 2n 2n的链,即任意选择一条边删除,把剩下的链复制一倍接在末尾。此时把长度为 n n n 的区间 [ i , i + n − 1 ] ( 1 ≤ i ≤ n ) [i,i+n-1](1 \leq i \leq n) [i,i+n1](1in合并成一个点,就相当于把编号为 i i i的边断开,再合并成一个点。这样的话我们避免了枚举最开始要删除那条边,每个阶段的状态从 n n n 变为了 2 n 2n 2n ,时间复杂度 O ( n 3 ) O(n^3) O(n3)

代码

#include <cstdio>
#include <algorithm>
#include <cmath>
#include <climits>
#include <cstring>
using namespace std;
const int MAXN = 105, inf = 32770;
char op[MAXN], s[MAXN];
int a[MAXN], n, dp[MAXN][MAXN][2], r, maxx = -inf;  //  dp[...][...][0]:最大值 dp[...][...][1]:最小值
void read(int &x) {
    int f = 1;
    x = 0;
    char c = getchar();
    while (c < '0' || c > '9') {
        if (c == '-')
            f *= -1;
        c = getchar();
    }
    while (c >= '0' && c <= '9') {
        x = x * 10 + (c - '0');
        c = getchar();
    }
    x *= f;
}
void Make_() {
    for (int i = 1; i <= n; i++) {
        dp[i][i][0] = dp[i][i][1] = dp[i + n][i + n][0] = dp[i + n][i + n][1] = a[i];
    }
    for (int i = 1; i <= (n << 1); i++) {
        for (int j = 1; j <= (n << 1); j++) {
            if (i != j) {
                dp[i][j][0] = -inf;
                dp[i][j][1] = inf;
            }
        }
    }
}
int main() {
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) {
        scanf("%s", s);
        op[i] = op[i + n] = s[0];
        read(a[i]);
        a[i + n] = a[i];
    }
    Make_();
    for (int len = 2; len <= n; len++) {
        for (int l = 1; l <= ((n << 1) - len); l++) {
            r = l + len - 1;
            for (int k = l; k < r; k++) {
                if (op[k + 1] == 'x') {
                    dp[l][r][0] = max(dp[l][r][0], dp[l][k][0] * dp[k + 1][r][0]);
                    dp[l][r][0] = max(dp[l][r][0], dp[l][k][1] * dp[k + 1][r][1]);
                    dp[l][r][1] = min(dp[l][r][1], dp[l][k][1] * dp[k + 1][r][1]);
                    dp[l][r][1] = min(dp[l][r][1], dp[l][k][0] * dp[k + 1][r][1]);
                    dp[l][r][1] = min(dp[l][r][1], dp[l][k][1] * dp[k + 1][r][0]);
                } else {
                    dp[l][r][0] = max(dp[l][r][0], dp[l][k][0] + dp[k + 1][r][0]);
                    dp[l][r][1] = min(dp[l][r][1], dp[l][k][1] + dp[k + 1][r][1]);
                }
            }
        }
    }
    for (int i = 1; i <= n; i++) {
        maxx = max(maxx, dp[i][i + n - 1][0]);
    }
    printf("%d\n", maxx);
    for (int i = 1; i <= n; i++) {
        if (dp[i][i + n - 1][0] == maxx)
            printf("%d ", i);
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值