关于石子合并

有n堆石子排成一列,每堆石子有一个重量w[i], 每次合并可以合并相邻的两堆石子,一次合并的代价为两堆石子的重量和w[i]+w[i+1]。问安排怎样的合并顺序,能够使得总合并代价达到最小。

//石子如果能交换顺序的话就是哈夫曼树了

//但是不能交换的话我们就只能考虑合并的顺序了,由于这个题目有贪心的嫌疑。。我们研究一下相邻两组合并的性质(看原子操作划分元素咯)

比如说1 2 3

如果你先合并1 2,sum+=(1+2),然后再合并另一部分。。代价是(1+2)+3,sum+=(1+2)+3,最终sum=9

如果你先合并2 3,sum+=(2+3),然后再合并另一部分。。代价是1+(2+3),sum+=1+(2+3),最终sum=11

所以我们找到了这个最简单的情况之后,就能递归地找最小

因为情况多变。。你永远不知道往哪里走才好。。只有动态决策才行

但是呢。。这里有一个思考的坑。。或者说还不清晰。。我来实力分析一波。。

1 2 3 4 

我先来一波错误思想。。。每次合并最小的

(1+2) sum+=3

3 3 4

(3+3) sum+=6

6 4

只剩两个直接合并

(6+4)sum+=10

sum=19

我再来一波探索的思想

因为最小考虑的单元是3嘛。。所以你现在困惑的应该是1,2,3先合并哪个,2,3,4先合并哪个

考虑划分1,2,3,前面算过。。是先1+2比较好,然后再合并3,3 ,最后合并6,4  sum=3+6+10

考虑2,3,4,先加2,3比较好,然后再合并5,4,最后合并1,9 sum+=5+9+10

没推翻错误想法啊。。再造它一组数据好了

4 2 1 3

1 2 4 2 1 3 1 2

+3+3+3

3 4 3 3 3

+6

3 4 3 6

发现歧义。。合并哪边呢

假如是从前往后遍历的第一个最小的

那么就是

+7

7 3 6

+9

7 9

+16

16

sum=47

====

3 4 3 6

合并后面那个4 3的话

+7

3 7 6

+10

10 6

+16

sum=48

显然这两者的选择造成的结果不同。。而我们的最优决策则认为它们是一样的。。

这个信息真的是。。你跑过之后才知道。。不跑不知道的。。

其实主要是 3 4 3 6

主要是合并成 7 3 6,3 7 6的不同

一组能取到3,6,一组取不到3,6

 如果你要是写记忆化搜索的话

它的代码是这个样子

#include <iostream>
#include <cstdio>
using namespace std;
const int maxn=1e2+7;
int n;
int w[maxn];
int sum[maxn];
// 1 2 3
// (1+2)+((1+2)+3)=9
// (2+3)+((2+3)+1)=11
//可以看得出这个题的重叠子问题性质非常明显
int dp[maxn][maxn];
int dfs(int st,int end){
    int len=end-st+1;
    if(len==1){
        return dp[st][end]=0;
    }
    if(dp[st][end]>0) return dp[st][end];
    if(len==2){
        return dp[st][end]=w[st]+w[end];
    }
    int i,j;
    int ans=~0u>>1;
    for(i=st;i<end;++i){
        //st i sum[i]-sum[st-1];
        //i+1 end sum[end]-sum[i];
        //合并的代价加上前缀和不就好了吗
        int pre1=sum[i]-sum[st-1];
        int pre2=sum[end]-sum[i];
        //实际上pre1+pre2可以消去sum[i],然后这个十字就变成sum[end]-sum[st-1];
        ans=min(ans,dfs(st,i)+pre1+dfs(i+1,end)+pre2);
    }
    return dp[st][end]=ans;
}
int main(){
    scanf("%d",&n);
    int i;
    for(i=1;i<=n;++i){
        scanf("%d",&w[i]);
    }
    for(i=1;i<=n;++i){
        sum[i]=sum[i-1]+w[i];
    }
    printf("%d",dfs(1,n));
    
    return 0;
}

不加记忆化这里会特别慢。。因为对于这个问题有大量的重叠子问题,优化空间很大,狗哥说不加dp记忆化至少要比不加快5,6倍

但是根据白神的经验,对于有的问题可能加了记忆化反而会超时,我觉得这是有可能的,对于重叠子问题不太多的搜索,如果你每次都

记忆化时间复杂度会多出来一个返回值每次回溯的赋值,如果这个操作量很大的话,就会引起超时。所以对于这个问题我们是要具体问题具体分析

那我们来看一看如何把一个记忆化搜索改成递推,那就是我们要看清楚这棵树的拓扑结构,倒数第二次回溯都依赖哪些状态的计算结果,那么

我们把这些终止状态找到并且赋值,这个判断的属性都会写到dfs里面所以我们可以枚举他。

如果我们要先计算出所有某一阶段的值,下一阶段的计算又依赖于上一阶段的计算,那么我们的做法就是把它放到最外面的循环,只有这样

才能一个阶段算完再算另一个阶段,可以看一下我下面这个枚举len长度进行递推的dp,for循环中枚举的每一维的状态你要清楚

for循环的先后状态所导致的拓扑结构你也要想清楚,如果稀里糊涂的话,你会wa到妈妈都不认识的

#include <iostream>
#include <cstdio>

using namespace std;
int n;
const int maxn=1e2+7;
int w[maxn];
int dp[maxn][maxn];
int sum[maxn];
int main(){
    scanf("%d",&n);
    int i,j,k;
    for(i=1;i<=n;++i){//注意这里的下标与前缀和的求法
        scanf("%d",&w[i]);
        sum[i]=sum[i-1]+w[i];
    }
    for(j=0;j<n;++j){
        //第一维枚举长度
        for(i=1;i<=n&&i+j<=n;++i){
            //第二维枚举起点
            if(j==0){
                dp[i][i+j]=0;
                continue;    
            }
            if(j==1){
                dp[i][i+j]=w[i]+w[i+j];
                continue;
            }
            int st=i;
            int end=i+j;
            dp[i][i+j]=~0u>>1;
            for(k=i;k<end;++k){
                //第三维枚举分割点
                int pre1=sum[k]-sum[i-1];
                int pre2=sum[i+j]-sum[k];
                dp[i][i+j]=min(dp[i][i+j],dp[i][k]+pre1+dp[k+1][i+j]+pre2);
            }
        }
    }
    printf("%d\n",dp[1][n]);
     return 0;
}
    // for(i=1;i<=n;++i){
    //     for(j=0;i+j<=n;++j){
    //         int cur=i+j;
    //         if(j==0) dp[i][cur]=w[i];
    //         else if(j==1) dp[i][cur]=w[i]+w[cur];
    //         else{
    //             dp[i][cur]=~0u>>1;
    //             for(k=i;k<cur;++k){
    //                 int pre1=sum[k]-sum[i-1];
    //                 int pre2=sum[cur]-sum[k];
    //                 dp[i][cur]=min(dp[i][cur],dp[i][k]+pre1+dp[k+1][cur]+pre2);
//                    //依赖的东西还没有算完,我们需要一层一层地算
    //             }
    //         }
    //     }
    // }

所以说,不要看到dp[i][k]和dp[k+1][j]+w[i][j]这种形式就想着把k放到最后一维去枚举。。你这样,太naive!

实际上我们还可以发现,递推的顺序与记忆化搜索的顺序是不一样的,递推还需要进一步的思考,把记忆化搜素分析得有条理

分析成一层一层的才方便我们递推

posted @ 2016-11-23 18:15  狡啮之仰  阅读(624)  评论(0编辑  收藏  举报