POJ 1179 Polygon 区间DP (详细题解)

本文详细介绍了POJ 1179题目的解题思路,这是一个关于多边形游戏的益智问题。玩家通过选择删除边来合并顶点并计算得分。题解中提出了使用动态规划的解决方案,包括区间DP状态的设计,以及如何优化时间复杂度从O(N^4)降至O(N^3)。最后提供了AC代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Polygon

题意

“多边形游戏” 是一款单人益智游戏。在游戏开始时,系统给定玩家一个N边形,该N边形由N个顶点和N条边构成,每条边连接两个相邻的顶点。在每个顶点上写有一个整数,可正可负。在每条边上标有一个运算符“+”(加号)或“*” (乘号)。
第一步,玩家需要选择一条边,将它删除。接下来再进行N-1步,在每一步中玩家选择一条边, 把这条边以及该边连接的两个顶点用一个新的顶点代替,新顶点上的整数值等于删去的两个顶点上的数按照删去的边上标有的符号进行计算得到的结果。最终剩下一个顶点,顶点上的数值就是玩家的得分。

请计算对于给定的N边形,玩家最高能获得多少分,以及第一步有哪些策略可以使玩家获得最高得分。
1≤N≤50,保证玩家无论如何操作,顶点上的数值均在[- 32768,32767]之内。

题解

首先容易想到枚举删除每一条边,dp[l][r]表示把第l到r的顶点合成之后最大的数值。但是把顶点上最大的数值作为子问题的解对于这一题是不行的,因为最大的数值也可以是2个最小的负数相乘得到,但是如果把最大和最小值同时作为子问题的信息,就可以满足最优子结构了。

最大值只能是2个最大值相加或者相乘,或者2个最小值相乘。

最小值只能是2个最小值相加或者相乘,或者一个最大值和一个最小值相乘,或者2个最大值相乘(当2个子区间的最大最小值全为负数时)。
dp[l][r][0] 为区间[l,r] 的最大值, dp[l][r][1] 为区间[l,r] 的最小值;
dp[l,r,0]=max⁡l≤k&lt;r{dp[l,k,0]&ThinSpace;op&ThinSpace;dp[k+1,r,0]op∈{+,*} dp[l,k,1]&ThinSpace;∗&ThinSpace;dp[k+1,r,1] dp[l,r,0] = \max\limits_{ l \le k&lt;r } \left\{ \begin{array}{ll} dp[l,k,0 ]\,op \,dp[k+1,r,0] &amp; \textrm{op$\in $\{+,*\} }\\ dp[l,k,1 ]\,* \,dp[k+1,r,1] \end{array} \right. dp[l,r,0]=lk<rmax{dp[l,k,0]opdp[k+1,r,0]dp[l,k,1]dp[k+1,r,1]op{+,*} 
dp[l,r,1]=min⁡l≤k&lt;r{dp[l,k,1]&ThinSpace;op&ThinSpace;dp[k+1,r,1]op∈{+,*} dp[l,k,0]&ThinSpace;∗&ThinSpace;dp[k+1,r,1]dp[l,k,1]&ThinSpace;∗&ThinSpace;dp[k+1,r,0]dp[l,k,0]&ThinSpace;∗&ThinSpace;dp[k+1,r,0] dp[l,r,1] = \min\limits_{ l \le k&lt;r } \left\{ \begin{array}{ll} dp[l,k,1 ]\,op \,dp[k+1,r,1] &amp; \textrm{op$\in $\{+,*\} }\\ dp[l,k,0 ]\,* \,dp[k+1,r,1] \\ dp[l,k,1 ]\,* \,dp[k+1,r,0] \\ dp[l,k,0 ]\,* \,dp[k+1,r,0] \\ \end{array} \right. dp[l,r,1]=lk<rmindp[l,k,1]opdp[k+1,r,1]dp[l,k,0]dp[k+1,r,1]dp[l,k,1]dp[k+1,r,0]dp[l,k,0]dp[k+1,r,0]op{+,*} 
上述算法的时间复杂度为O(N4N^4N4)。实际上我们还可以进一步优化掉枚举第一步删除哪条边耗费的
时间。在游戏最初,我们任意选择一条边删除, 然后把剩下的“链”复制一倍接在末尾(以被删除的边逆时针方向的第一个顶点为开头),在这个长度为2N的 “链”上,Vi∈[1,N],把长度为N的区间[i,i+N-1]合并成一个顶点,就等价于原游戏的第一步删除第 i个顶点逆时针一侧的边,然后把剩余的部分合并成一个顶点。 因为区间长度是DP的阶段,我们只需要对前N个阶段进行DP,每个阶段只有不超过2N个状态,总时间复杂度降低为0(N3)。 最后的答案是 max⁡1≤i≤Nf[i,i+N−1]\max\limits_{1\le i\le N} f[i,i+N-1]1iNmaxf[i,i+N1];

这种 “任意选择一个位置断开, 复制形成2倍长度的链”的方法,是解决DP中环形结构的常用手段之一。

AC代码:

#include <iostream>
using namespace std;
#define ll long long
const int maxn = 2e2;
int dp[maxn][maxn][2];
char op[maxn];
int num[maxn];
int main()
{
//    ios::sync_with_stdio(false);
//    freopen("in.txt", "r", stdin);
    int n;
    cin >> n;
    for(int i = 1; i <= n; i++)
        cin >> op[i - 1] >> num[i];
        
    for(int i = n + 1; i <= 2 * n; i++)
    {
        op[i - 1] = op[i - n - 1];
        num[i] = num[i - n];
    }
//    for(int i = 1;i<=2*n;i++)cout<<num[i]<<" "<<op[i]<<" ";

    for(int i = 0; i <= 2 * n; i++)
    {
        for(int j = 0; j <= 2 * n; j++)
        {
            dp[i][j][0] = -0x3f3f3f3f;
            dp[i][j][1] = 0x3f3f3f3f;
        }
    }
    for(int i = 1; i <= 2 * n; i++) dp[i][i][0] = dp[i][i][1] = num[i];//初始化边界
    
    for(int len = 2; len <= n; len++)//阶段
    {
        for(int l = 1; l <= 2 * n - len + 1; l++)//状态:左端点
        {
            int r = l + len - 1;//右端点
            for(int k = l; k < r; k++)//决策
            {
                int maxx = -0x3f3f3f3f;
//                cout<< dp[l][k][0] <<" "<< dp[k + 1][r][0]<<endl;
                if(op[k] == 't')
                    maxx = max(maxx, dp[l][k][0] + dp[k + 1][r][0]);
                else maxx = max(maxx, dp[l][k][0] * dp[k + 1][r][0]);
                if(op[k] == 'x')
                    maxx = max(maxx, dp[l][k][1] * dp[k + 1][r][1]);
                maxx = max(maxx, dp[l][r][0]);
                dp[l][r][0] = maxx;

                int minn = 0x3f3f3f3f;
                if(op[k] == 't')
                    minn = min(minn, dp[l][k][1] + dp[k + 1][r][1]);
                else minn = min(minn, dp[l][k][1] * dp[k + 1][r][1]);
                if(op[k] == 'x')
                {
                    minn = min(minn, dp[l][k][1] * dp[k + 1][r][0]);
                    minn = min(minn, dp[l][k][0] * dp[k + 1][r][1]);
                    minn = min(minn, dp[l][k][0] * dp[k + 1][r][0]);
                }
                minn = min(minn, dp[l][r][1]);
                dp[l][r][1] = minn;
                
            }
        }
    }
    
    int ans = -0x3f3f3f3f;
    for(int i = 1; i <= n; i++)
    {
        ans = max(dp[i][i + n - 1][0], ans);
        ans = max(dp[i][i + n - 1][1], ans);
    }
    cout << ans << endl;
    
    int i;
    for(i = 1; i <= n; i++)
        if(dp[i][i + n - 1][0] == ans || dp[i][i + n - 1][1] == ans)
        {
            cout << i;
            break;
        }
        
    i++;
    for(; i <= n; i++)
        if(dp[i][i + n - 1][0] == ans || dp[i][i + n - 1][1] == ans)
            cout << " " << i;
    cout << endl;
    
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值