算法設計與分析——多邊形游戲(動態規划)


一、問題描述

多邊形游戲是一個單人玩的游戲,開始時有一個由n個頂點構成的多邊形。每個頂點被賦予一個整數值,每條邊被賦予一個運算符“+”或“*”。所有邊依次用整數從1到n編號。

  游戲第1步,將一條邊刪除。

  隨后n-1步按以下方式操作:

  (1)選擇一條邊E以及由E連接着的2個頂點V1和V2;

  (2)用一個新的頂點取代邊E以及由E連接着的2個頂點V1和V2。將由頂點V1和V2的整數值通過邊E上的運算得到的結果賦予新頂點。

  最后,所有邊都被刪除,游戲結束。游戲的得分就是所剩頂點上的整數值。

  問題:對於給定的多邊形,計算最高得分。

如下圖:

 

其實該問題與之前討論過的凸多邊形最優三角剖分問題是類似的,但二者的最優子結構性質卻不同。多邊形游戲問題的最優子結構性質更具有一般性。

二、算法思路

1、最優子結構性質

設所給的多邊形的頂點和邊的順時針序列為op[1],v[1],op[2],v[2],op[3],…,op[n],v[n] 其中,op[i]表示第i條邊所對應的運算符,v[i]表示第i個頂點上的數值,i=1~n。
       在所給的多邊形中,從頂點i(1<=i<=n)開始,長度為j(鏈中有j個頂點)的順時針鏈p(i,j)可表示為v[i],op[i+1],…,v[i+j-1],如果這條鏈的最后一次合並運算在op[i+s]處發生(1<=s<=j-1),則可在op[i+s]處將鏈分割為兩個子鏈p(i,s)和p(i+s,j-s)
    設m[i,j,0]是鏈p(i,j)合並的最小值,而m[i,j,1]是最大值。若最優合並在op[i+s]處將p(i,j)分為兩個長度小於j的子鏈的最大值和最小值均已計算出。即:
    a=m[i,s,0]  b=m[i,s,1]  c=m[i+s,j-s,0]  d=m[i+s,j-s,1]


   (1) 當op[i+s]=’+’時
    m[i,j,0]=a+c ;m[i,j,1]=b+d

   該鏈的最優性由子鏈的最優性決定,最大值對應於子鏈的最大值,最小值對應於子鏈的最小值。

   (2) 當op[i+s]=’*’時
    m[i,j,0]=min{ac,ad,bc,bd} ; m[i,j,1]=max{ac,ad,bc,bd}
   由於v[i]可能取負數,子鏈的最大值相乘未必能得到主鏈的最大值,但是注意到,主鏈的最大值和最小值可以由子鏈的最大最小值得到。


    由於最優斷開位置s有1<=s<=j-1的j-1中情況。 初始邊界值為 m[i,1,0]=v[i]   1<=i<=n m[i,1,1]=v[i]   1<=i<=n

2、遞歸求解

可以得到遞歸表達式,將p(i,j)在op[i+s]處斷開的最大值記為maxf(i,j,s),最小值記為minf(i,j,s)則:

因為多變形式封閉的,在上面的計算中,當i+s>n時,頂點i+s實際編號為(i+s)mod n。按上述遞推式計算出的m[i,n,1]記為游戲首次刪除第i條邊后得到的最大得分。

3、算法描述

void MinMax(int n,int i,int s,int j,int &minf,int &maxf)
{
    int e[5];
    int a=m[i][s][0],b=m[i][s][1];
    int r=(i+s-1)%n+1;//多邊形的實際頂點編號
    int c=m[r][j-s][0],d=m[r][j-s][1];

    if(op[r]=='t')
    {
        minf=a+c;
        maxf=b+d;
    }
    else
    {
        e[1]=a*c;
        e[2]=a*d;
        e[3]=b*c;
        e[4]=d*b;
        minf=e[1];
        maxf=e[1];
        for(int r=2; r<5; r++)
        {
            if(minf>e[r])
                minf=e[r];
            if(maxf<e[r])
                maxf=e[r];
        }
    }
}

int PloyMax(int n,int& p)
{
    int minf,maxf;
    for(int j=2; j<=n; j++) //迭代鏈的長度
    {
        for(int i=1; i<=n; i++) //迭代首次刪掉第i條邊
        {
            for(int s=1 ; s<j; s++) //迭代斷開位置
            {
                MinMax(n,i,s,j,minf,maxf);
                if(m[i][j][0]>minf)
                    m[i][j][0]=minf;
                if(m[i][j][1]<maxf)
                    m[i][j][1]=maxf;
            }
        }
    }

    int temp=m[1][n][1];
    p=1;
    for(int i=2 ; i<=n; i++)
    {
        if(temp<m[i][n][1])
        {
            temp=m[i][n][1];
            p=i;
        }
    }
    return temp;
}

4、計算復雜性分析

與凸多邊形最優三角剖分問題類似,上述算法需要O(n3)計算時間。

5、例題

POJ1197

#include<iostream>
#include<algorithm>
#define MAX 200
#define INF 0x7fffffff
using namespace std;

int m[MAX+1][MAX+1][2];
int v[MAX+1];
int out[MAX+1];
char op[MAX+1];
int ans=-INF;
int minf,maxf;
int n;
void MinMax(int i,int s,int j)
{
    int e[5];
    int a=m[i][s][0];
    int b=m[i][s][1];
    int r=(i+s-1)%n+1;//多邊形的實際頂點編號
    int c=m[r][j-s][0];
    int d=m[r][j-s][1];
    if(op[r]=='t')
    {
        minf=a+c;
        maxf=b+d;
    }
    else
    {
        e[1]=a*c;
        e[2]=a*d;
        e[3]=b*c;
        e[4]=d*b;
        minf=e[1];
        maxf=e[1];
        for(int k=2; k<5; k++)
        {
            maxf=max(maxf,e[k]);
            minf=min(minf,e[k]);
        }
    }
}

int main()
{
    scanf("%d",&n);
    getchar();
    for(int i=1; i<=n; i++)
    {
        scanf("%c %d",&op[i],&v[i]);
        m[i][1][1]=v[i];
        m[i][1][0]=v[i];
        getchar();
    }
    for(int j=2; j<=n; j++)//迭代鏈的長度
    {
        for(int i=1; i<=n; i++) //迭代首次刪掉的邊
        {
            for(int s=1; s<j; s++) //迭代斷開位置
            {
                MinMax(i,s,j);
                if(s==1)
                {
                    m[i][j][0]=minf;
                    m[i][j][1]=maxf;
                }
                else
                {
                    m[i][j][1]=max(maxf,m[i][j][1]);
                    m[i][j][0]=min(minf,m[i][j][0]);
                }
            }

        }
    }
    int i;
    int cnt=0;
    for(i=1; i<=n; i++)
    {
        if(m[i][n][1]>ans)
        {
            ans=m[i][n][1];
        }
    }
    for(i=1; i<=n; i++)
    {
        if(m[i][n][1]==ans)
        {
            cnt++;
            out[cnt]=i;
        }
    }
    printf("%d\n",ans);
    for(i=1; i<cnt; i++)
    {
        printf("%d ",out[i]);
    }
    printf("%d\n",out[i]);
    return 0;
}

 再附上幾組數據,方便調試:

5
x 2 x 3 t 1 t 7 x 4

224
4


5
x -3 t -1 t -7 t -4 x -2

30
1 5


3
t 0 x 1 t -2

0
1


30
x 1 t 1 x 1 t 1 t 1 x 1 x 1 x 1 x 1 x 1 x 1 t 1 t 1 x 1 t 1 x 1 x 1 t 1 x 1 x 1 t 1 x 1 x 1 x 1 x 1 x 1 t 1 x 1 x 1 x 1

288
1 3 6 7 8 9 10 11 14 16 17 19 20 22 23 24 25 26 28 29 30


48
x 1 x 2 x 1 x -1 t 1 x -1 x -1 x 1 t 1 t -1 x 1 t 2 x 1 x 2 t 1 x 1 x -1 x -2 x 1 x 1 t 1 x 1 t 1 x 1 x 1 x 1 t 1 x 1 x 1 x 1 x 1 x 1 x 1 x -1 t 1 x 1 x -1 x -1 t 1 x 1 t 1 x 1 x 1 x -1 t 1 t -1 t -1 x 1

23328
45

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM