1. 程式人生 > 實用技巧 >Acwing 283.多邊形 (區間DP)

Acwing 283.多邊形 (區間DP)

題面

“多邊形遊戲”是一款單人益智遊戲。

遊戲開始時,給定玩家一個具有N個頂點N條邊(編號1-N)的多邊形,如圖1所示,其中N = 4。

每個頂點上寫有一個整數,每個邊上標有一個運算子+(加號)或運算子*(乘號)。

第一步,玩家選擇一條邊,將它刪除。

接下來在進行N-1步,在每一步中,玩家選擇一條邊,把這條邊以及該邊連線的兩個頂點用一個新的頂點代替,新頂點上的整數值等於刪去的兩個頂點上的數按照刪去的邊上標有的符號進行計算得到的結果。

最終,遊戲僅剩一個頂點,頂點上的數值就是玩家的得分,上圖玩家得分為0。

請計算對於給定的N邊形,玩家最高能獲得多少分,以及第一步有哪些策略可以使玩家獲得最高得分。

輸入格式
輸入包含兩行,第一行為整數N。

第二行用來描述多邊形所有邊上的符號以及所有頂點上的整數,從編號為1的邊開始,邊、點、邊…按順序描述。

其中描述頂點即為輸入頂點上的整數,描述邊即為輸入邊上的符號,其中加號用“t”表示,乘號用“x”表示。

輸出格式
輸出包含兩行,第一行輸出最高分數。

在第二行,將滿足得到最高分數的情況下,所有的可以在第一步刪除的邊的編號從小到大輸出,資料之間用空格隔開。

資料範圍
3≤N≤50,
資料保證無論玩家如何操作,頂點上的數值均在[-32768,32767]之內。

輸入樣例:
4
t -7 t 4 x 2 x 5
輸出樣例:
33
1 2

思路

我們很容易注意到它是一道區間dp,因為它和石子合併真的很像,但我們可以看出這裡的難點在於這是一個環,我們要首先割去一條邊,才會有一個比較明顯的區間dp模型出現,第二個難點,我們在取值的時候,因為乘法的特殊性,我們無法保證兩邊都取最大值的時候,我得到的一定是最大值。關於第一點的話,我們這裡有一個在區間dp和環形問題裡面常用的一個技巧:破環成鏈,我們從一開始把這個環展開,然後在末尾端接上一個一樣的展開鏈,那麼這樣我們就可以在這一段區間裡面取到我們想要的區間了,而不用考慮如何割邊的問題。第二個點的話,我們可以多加一個dp陣列維護最小值,然後在狀態轉移的時候對左右區間窮盡所有可能,取其中的最大最小值更新就可以了。剩下的就是區間dp的框架了,列舉區間長度和起點。

程式碼實現

#include<cstdio>
#include<algorithm>
#include<vector>
#include<queue>
#include<map>
#include<iostream>
#include<cstring>
#include<cmath>
using namespace std;
#define rep(i,f_start,f_end) for (int i=f_start;i<=f_end;++i)
#define per(i,n,a) for (int i=n;i>=a;i--)
#define MT(x,i) memset(x,i,sizeof(x) )
#define rev(i,start,end) for (int i=0;i<end;i++)
#define inf 0x3f3f3f3f
#define mp(x,y) make_pair(x,y)
#define lowbit(x) (x&-x)
#define MOD 1000000007
#define exp 1e-8
#define N 1000005 
#define fi first 
#define se second
#define pb push_back
typedef long long ll;
typedef pair<int ,int> PII;
typedef pair<int ,PII> PIII;
ll gcd (ll a,ll b) {return b?gcd (b,a%b):a; }
inline int read() {
    char ch=getchar(); int x=0, f=1;
    while(ch<'0'||ch>'9') {
        if(ch=='-') f = -1;
        ch=getchar();
    } 
    while('0'<=ch&&ch<='9') {
        x=x*10+ch-'0';
        ch=getchar();
    }   return x*f;
}

const int maxn=101;
int f[maxn][maxn];
int g[maxn][maxn];
int w[maxn];
char c[maxn];
int n;

int main () {
     cin>>n;
     rep (i,1,n) {
         char x;
         int y;
         cin>>x>>y;
         w[i]=w[i+n]=y;
         c[i]=c[i+n]=x;
     }
     rep (len,1,n) {
         for (int l=1;l+len-1<2*n;l++) {
             int r=l+len-1;
             if (l==r) f[l][r]=w[l],g[l][r]=w[l];
             else f[l][r]=-inf,g[l][r]=inf;
             rep (k,l,r-1) {
                char op=c[k+1];
                if (op=='t') {
                    int a=g[l][k],b=g[k+1][r],a2=f[l][k],b2=f[k+1][r];
                    f[l][r]=max (a2+b2,f[l][r]);
                    g[l][r]=min (a+b,g[l][r]);
                }
                else {
                    int a1=(f[l][k]*f[k+1][r]);
                    int b1=(f[l][k]*g[k+1][r]);
                    int c1=(g[l][k]*f[k+1][r]);
                    int d1=(g[l][k]*f[k+1][r]);
                    int maxnum=max (max (a1,b1),max (c1,d1));
                    int minnum=min (min (a1,b1),min (c1,d1));
                    f[l][r]=max (f[l][r],maxnum);
                    g[l][r]=min (g[l][r],minnum);
                }
             }
         }
     }
    int ans=-inf;
    rep (i,1,n) {
      ans=max (ans,f[i][i+n-1]);
    }
    cout<<ans<<endl;
    rep (i,1,n) 
        if (ans==f[i][i+n-1])  cout<<i<<" ";
        
    return 0; 
}