石子合並


石子合並

   有N堆石子排成一排,每堆石子有一定的數量。現要將N堆石子並成為一堆。合並的過程只能每次將相鄰的兩堆石子堆成一堆,每次合並花費的代價為這兩堆石子的和,經過N-1次合並后成為一堆。求出總的代價最小值。

 


 

 

 

  石子合並是一道十分經典的問題。注意到石子的合並可以當做區間的合並,顯而易見的,我們有如下狀態轉移方程:

  

 

  程序也十分簡單:

#include<bits/stdc++.h>
using namespace std;
#define MAXN 220
const int INF=0x3f3f3f3f;
int n,w[MAXN],sum[MAXN],dp[MAXN][MAXN];
int main(){
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%d",&w[i]);
        sum[i]=sum[i-1]+w[i];
    }
    for(int i=n;i>=1;i--){
        for(int j=i+1;j<=n;j++){
            int tmp=INF;
            for(int k=i;k<j;k++)
                tmp=min(tmp,dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1]);
            dp[i][j]=tmp;
        }
    }
    printf("%d\n",dp[1][n]);
    return 0;
}

  以上的算法正確性是可以保證的,但時間復雜度達到了O(n3),當n≥1000時,這樣的時間復雜度是無法接受的。

 


 

  考慮進一步優化。

  回顧它的轉移方程:

  

  根據平行四邊形不等式,我們可以從dp[i][j-1]的決策點dp[i+1][j]的決策點枚舉,時間復雜度是O(n2)

  

  代碼如下:

#include<bits/stdc++.h>
using namespace std;
#define MAXN 5000+10
typedef long long LL;
const LL INF=0xffffff;
int n,g[MAXN][MAXN];
LL a[MAXN],sum[MAXN],f[MAXN][MAXN];
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++)scanf("%lld",&a[i]);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            f[i][j]=INF;
    for(int i=1;i<=n;i++){
        sum[i]=sum[i-1]+a[i];
        f[i][i]=0;
        g[i][i+1]=i;
    }
    for(int i=1;i<=n;i++)f[i][i+1]=sum[i+1]-sum[i-1];
    for(int i=n-2;i;i--)
        for(int j=i+2;j<=n;j++)
            for(int k=g[i][j-1];k<=g[i+1][j];k++){
                int tmp=f[i][k]+f[k+1][j]+sum[j]-sum[i-1];
                if(f[i][j]>tmp){
                    f[i][j]=tmp;
                    g[i][j]=k;
                }
            }
    printf("%lld",f[1][n]);        
    return 0;
}

  盡管有了進一步的優化,但對於更大的數據,此算法也無能為力。(Codevs 2298

  


 

  實際上對於石子歸並問題還有另一種算法:GarsiaWachs算法

  算法的大致流程如下:

    1.設一個序列是A[0..n-1],每次尋找最小的一個滿足A[k-1]<=A[k+1]的k,把A[k]與A[k-1]合並

    2.之后找最大的一個滿足A[j]>A[k]+A[k-1]的j,把合並后的值A[k]+A[k-1]插入A[j]的后面。

  由於此算法在其他題目中較少用到,在此不多贅述。可以證明,它的時間復雜度是O(nlogn)

  

  代碼如下:  

#include<bits/stdc++.h>
using namespace std;
#define MAXN 50000+10
typedef long long LL;
int t,n,a[MAXN];
LL ans=0;
void work(int k){
    int tmp=a[k-1]+a[k];
    ans+=tmp;
    for(int i=k;i<t-1;i++)a[i]=a[i+1];
    t--;
    int j=0;
    for(j=k-1;j>0&&a[j-1]<tmp;j--)a[j]=a[j-1];
    a[j]=tmp;
    while(j>=2&&a[j]>=a[j-2]){
        int d=t-j;
        work(j-1);
        j=t-d;
    }
}
int main(){
    scanf("%d",&n);
    ans=0;
    for(int i=0;i<n;i++)scanf("%d",&a[i]);
    t=1;
    for(int i=1;i<n;i++){
        a[t++]=a[i];
        while (t>=3&&a[t-3]<=a[t-1])
            work(t-2);
    }
    while(t>1)work(t-1);
    printf("%lld\n",ans);
    return 0;
}

 


 

  

  對於石子合並問題我只給出以上三種,孰優孰劣,大家見仁見智。


免責聲明!

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



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