zoukankan      html  css  js  c++  java
  • [NOI1995]石子合并 题解

    一道经典的dp题

    在一个圆形操场的四周摆放N堆石子,现要将石子有次序地合并成一堆.规定每次只能选相邻的2堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分。

    试设计出1个算法,计算出将N堆石子合并成1堆的最小得分和最大得分.

    我们先看下这道题的简单版本

    有N堆石子排成一排,每堆石子有一定的数量。现要将N堆石子并成为一堆。合并的过程只能每次将相邻的两堆石子堆成一堆,每次合并花费的代价为这两堆石子的和,经过N-1次合并后成为一堆。求出总的代价最小值。

    这道题不是环状的,我们可以直接dp解决,一开始我设的是f[i][j]表是合并i-j这个区间内的最小代价,于是有了状态转移方程

    f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]+s[j]-s[i-1]) (i<=k<=j)

    于是写下了下面的代码

    #include<bits/stdc++.h>
    using namespace std;
    int n,f[110][110],a[110];
    int s[110];
    int main(){
       scanf("%d",&n);
       memset(f,0x3f,sizeof(f));
       for(int i=1;i<=n;++i){
           scanf("%d",&a[i]);
           f[i][i]=0;
           s[i]=s[i-1]+a[i];
       }
       for(int i=1;i<=n;++i){
           for(int j=i;j<=n;++j){
               for(int k=i;k<=j;++k){
                   f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]+s[j]-s[i-1]);
               }
           }
       }
       printf("%d",f[1][n]);
       return 0;
    } 
    

    然鹅答案错误,为什么?

    在同机房的大佬的帮助下我明白了

    因为求大区间是要用到小区间的值,可是上面这个程序固定了起点就一直向后跑会导致有些点更新过晚,要用的时候却用不到,于是我写下了下面的代码

    #include<bits/stdc++.h>
    using namespace std;
    int n,f[110][110],a[110],T=10;
    int s[110];
    int main(){
        scanf("%d",&n);
        memset(f,0x3f,sizeof(f));
        for(int i=1;i<=n;++i){
            scanf("%d",&a[i]);
            f[i][i]=0;
            s[i]=s[i-1]+a[i];
        }
        while(T--){
            for(int i=1;i<=n;++i){
                for(int j=i;j<=n;++j){
                    for(int k=i;k<=j;++k){
                        f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]+s[j]-s[i-1]);
                    }
                }
            }
        }
        printf("%d",f[1][n]);
        return 0;
    } 
    

    既然一遍跑不出答案,那我多跑几遍不就好了,同机房的大佬都震惊了,虽然答案是对的,但却并不是正解,正解应该是在外面枚举长度,再枚举起点,算出终点dp

    代码

    #include<bits/stdc++.h>
    using namespace std;
    int n,f[110][110],a[110],T=10;
    int s[110];
    int main(){
        scanf("%d",&n);
        memset(f,0x3f,sizeof(f));
        for(int i=1;i<=n;++i){
            scanf("%d",&a[i]);
            f[i][i]=0;
            s[i]=s[i-1]+a[i];
        }
        for(int L=2;L<=n;++L){
            for(int i=1;i<=n;++i){
                int j=i+L-1;
                for(int k=i;k<=j;++k){
                    f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]+s[j]-s[i-1]);
                }
            }
        }
        printf("%d",f[1][n]);
        return 0;
    } 
    

    回到 noi1995这道题,我们看到环便可直接加倍断环成链(套路),其余思路同上面相似

    #include<cstring>
    #include<cstdio>
    #include<algorithm>
    using namespace std;
    int n,f[410][410],g[410][410],a[410];
    int s[410],maxn,minn=1<<30;
    int main(){
    	scanf("%d",&n);
    	memset(f,0x3f,sizeof(f));
    	for(int i=1;i<=n;++i){
    		scanf("%d",&a[i]);
    		f[i][i]=0;
    		s[i]=s[i-1]+a[i];
    	}
    	for(int i=1;i<=n;++i){
    		s[i+n]=s[i+n-1]+a[i];
    		f[i+n][i+n]=0;//这个初始化一定要记得
    	}
    	for(int L=2;L<=n;++L){
    		for(int i=1;i<=n+n;++i){//起点可以枚举到2n
    			int j=i+L-1;
    			if(j>n*2) break;//不符合
    			for(int k=i;k<j;++k){
    				f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]+s[j]-s[i-1]);
    				g[i][j]=max(g[i][j],g[i][k]+g[k+1][j]+s[j]-s[i-1]);
    			}
    		}
    	}
    	for(int i=1;i<=n;++i){
    		maxn=max(maxn,g[i][i+n-1]);
    		minn=min(minn,f[i][i+n-1]);
    	}
    	printf("%d
    %d
    ",minn,maxn);
    	return 0;
    } 
    
  • 相关阅读:
    Oracle查询今天的数据(昨天、本周...)
    Windows添加删除 route
    大三寒假学习进度(九)
    大三寒假学习进度(八)
    大三寒假学习进度(七)
    大三寒假学习进度(六)
    大三寒假学习进度(五)
    《软件架构师应该知道的97件事》阅读笔记(一)
    大三寒假学习进度(四)
    大三寒假学习进度(三)
  • 原文地址:https://www.cnblogs.com/donkey2603089141/p/11414990.html
Copyright © 2011-2022 走看看