1. 程式人生 > 實用技巧 >NOI 1995 石子合併

NOI 1995 石子合併

NOI 1995 石子合併

洛谷傳送門

JDOJ傳送門

Description

在一個圓形操場的四周擺放著n 堆石子。現要將石子有次序地合併成一堆。規定每次只能選相鄰的2 堆石子合併成新的一堆,並將新的一堆石子數記為該次合併的得分。

試設計一個演算法,計算出將n堆石子合併成一堆的最小得分和最大得分。

Input

包含兩行,第1 行是正整數n(1<=n<=100),表示有n堆石子。

第2行有n個數,分別表示每堆石子的個數。

Output

輸出兩行。

第1 行中的數是最小得分;第2 行中的數是最大得分。

Sample Input

4 4 4 5 9

Sample Output

43 54


題解:

區間DP入門題。

中間需要一個小技巧,斷環成鏈(不是圖論的那個)(大霧

然後在二倍區間上跑一個字首和方便維護每次合併的價值。

需要注意的是DP初態,最小值\(dp1[i][i]\)的初態是0,因為單個無法合併。

所以在二倍區間上區間DP,最後再統計一遍答案即可。

程式碼:

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn=210;
const int INF=1e9;
int n;
int a[maxn],sum[maxn],dp1[maxn][maxn],dp2[maxn][maxn];
int ans1,ans2;
int main()
{
    scanf("%d",&n);
    memset(dp1,0x3f,sizeof(dp1));
    ans1=INF;
    ans2=-INF;
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        a[n+i]=a[i];
    }
    for(int i=1;i<=n*2;i++)
        dp1[i][i]=0;
    for(int i=1;i<=n*2;i++)
        sum[i]=sum[i-1]+a[i];
    for(int len=2;len<=n;len++)
        for(int i=1;i<=n*2-len+1;i++)
        {
            int j=i+len-1;
            for(int k=i;k<j;k++)
            {
                dp1[i][j]=min(dp1[i][j],dp1[i][k]+dp1[k+1][j]+(sum[j]-sum[i-1]));
                dp2[i][j]=max(dp2[i][j],dp2[i][k]+dp2[k+1][j]+(sum[j]-sum[i-1]));
            }
        }
    for(int i=1;i<=n;i++)
    {
        ans1=min(ans1,dp1[i][i+n-1]);
        ans2=max(ans2,dp2[i][i+n-1]);
    }
    printf("%d\n%d",ans1,ans2);
    return 0;
}