P1880 [NOI1995]石子合併

AndreaQ發表於2020-12-05

 

題目描述

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

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

輸入格式

資料的第 11 行是正整數 NN,表示有 NN 堆石子。

第 22 行有 NN 個整數,第 ii 個整數 a_iai​ 表示第 ii 堆石子的個數。

輸出格式

輸出共 22 行,第 11 行為最小得分,第 22 行為最大得分。

輸入輸出樣例

輸入 #1複製

4
4 5 9 4

輸出 #1複製

43
54

說明/提示

1\leq N\leq 1001≤N≤100,0\leq a_i\leq 200≤ai​≤20。

 

此題是線性合併石子(https://blog.csdn.net/wan1314mum/article/details/110728523 )的變形,只是石子環狀擺放。

思路:還是區間dp,只不過加了個環

以樣例為例:

4

4 5 9 4

同樣還是想辦法從環擷取一個間斷點,所以可以如下儲存資料,即我們也將資料儲存環狀。

 4 5 9 4 4 5 9

那麼這2n-1個資料包括n條鏈(n=4)

4 5 9 4

5 9 4 4 

9 4 4 5

4 4 5 9

我們就成功將環拆成鏈,最後再n條鏈中取一個最值就行了。

狀態轉移方程還是跟線性合併一樣。

#include<iostream>
#include<cstring>
#include<stdio.h>
using namespace std;
const int N=105;
int dp[2*N][2*N],n,sum[2*N],a[2*N],f[2*N][2*N];

int main()
{
    int n,len;
    cin>>n;
    memset(f,0x3f,sizeof(f));
    for(int i=1; i<=n; i++){
       cin>>a[i];
       a[i+n]=a[i];
    }
    for(int i=1; i<=2*n; i++){
       sum[i]=sum[i-1]+a[i];
       f[i][i]=0;//邊界值
    }
    for(int len=2; len<=n; len++){//區間長度
       for(int i=1; i<=2*n-len; i++){//這裡長度是2*n-1了 左端點
         int j=i+len-1;//右端點
         for(int k=i; k<j; k++){
             dp[i][j]=max(dp[i][j],dp[i][k]+dp[k+1][j]-sum[i-1]+sum[j]);//最大值
             f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]-sum[i-1]+sum[j]);//最大值
         }
       }
    }
    int maxn=0,mi=0x7fffffff;
    for(int i=1; i<=n; i++){
        maxn=max(maxn,dp[i][i+n-1]);
        mi=min(mi,f[i][i+n-1]);
    }
    cout<<mi<<endl;
    cout<<maxn<<endl;
    return 0;
}

 

 

相關文章