洛谷題單指南-動態規劃3-P4342 [IOI1998] Polygon

江城伍月發表於2024-05-15

原題連結: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])

  g[i][j] = min(g[i][j], f[i][k] *f[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;
}

相關文章