zoukankan      html  css  js  c++  java
  • 石子合并

    石子合并

       有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
    ",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
    ",ans);
        return 0;
    }

      

      对于石子合并问题我只给出以上三种,孰优孰劣,大家见仁见智。

  • 相关阅读:
    rs
    stm32f767 usoc3
    stm32f767 RTT 日志
    stm32f767 标准库 工程模板
    stm32f767 HAL 工程模板
    docker tab 补全 linux tab 补全
    docker anconda 依赖 下载 不了
    docker run 常用 指令
    linux scp 命令
    Dockerfile 常用参数说明
  • 原文地址:https://www.cnblogs.com/NINGLONG/p/7624787.html
Copyright © 2011-2022 走看看