poj 1738 An old Stone Game(garsiawachs算法)

Time Limit: 5000MS   Memory Limit: 30000K
Total Submissions: 3704   Accepted: 1044

Description

There is an old stone game.At the beginning of the game the player picks n(1<=n<=50000) piles of stones in a line. The goal is to merge the stones in one pile observing the following rules: 
At each step of the game,the player can merge two adjoining piles to a new pile.The score is the number of stones in the new pile. 
You are to write a program to determine the minimum of the total score. 

Input

The input contains several test cases. The first line of each test case contains an integer n, denoting the number of piles. The following n integers describe the number of stones in each pile at the beginning of the game. 
The last test case is followed by one zero. 

Output

For each test case output the answer on a single line.You may assume the answer will not exceed 1000000000.

Sample Input

1
100
3
3 4 3
4
1 1 1 1
0

Sample Output

0
17
8

 

下面是我copy的别人的(黑体的部分)
 而GarsiaWachs算法可以把时间复杂度压缩到O(nlogn)。
具体的算法及证明可以参见《The Art of Computer Programming》第3卷6.2.2节Algorithm G和Lemma W,Lemma X,Lemma Y,Lemma Z。
只能说一个概要吧:
设一个序列是A[0..n-1],每次寻找最小的一个满足A[k-1]<=A[k+1]的k,(方便起见设A[-1]和A[n]等于正无穷大)
那么我们就把A[k]与A[k-1]合并,之后找最大的一个满足A[j]>A[k]+A[k-1]的j,把合并后的值A[k]+A[k-1]插入A[j]的后面。
有定理保证,如此操作后问题的答案不会改变。
举个例子:
186 64 35 32 103
因为35<103,所以最小的k是3,我们先把35和32删除,得到他们的和67,并向前寻找一个第一个超过67的数,把67插入到他后面
186 64(k=3,A[3]与A[2]都被删除了) 103
186 67(遇到了从右向左第一个比67大的数,我们把67插入到他后面) 64 103
186 67 64 103 (有定理保证这个序列的答案加上67就等于原序列的答案)
现在由5个数变为4个数了,继续!
186 (k=2,67和64被删除了)103
186 131(就插入在这里) 103
186 131 103
现在k=2(别忘了,设A[-1]和A[n]等于正无穷大)
234 186
420
最后的答案呢?就是各次合并的重量之和呗。420+234+131+67=852,哈哈,算对了。
 
证明嘛,基本思想是通过树的最优性得到一个节点间深度的约束,之后
证明操作一次之后的解可以和原来的解一一对应,并保证节点移动之后他所在的
深度不会改变。详见TAOCP。
 
具体实现这个算法需要一点技巧,精髓在于不停快速寻找最小的k,即维护一个“2-递减序列”
朴素的实现的时间复杂度是O(n*n),但可以用一个平衡树来优化(好熟悉的优化方法),使得最终复杂度为O(nlogn)
 
 
解题思路:(这是我找到的一个关于GarsiaWachs算法的解释)
 
      1. 这类题目一开始想到是DP, 设dp[i][j]表示第i堆石子到第j堆石子合并最小得分.
 
         状态方程: dp[i][j] = min(dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1]);
 
         sum[i]表示第1到第i堆石子总和. 递归记忆化搜索即可.
 
      2. 不过此题有些不一样, 1<=n<=50000范围特大, dp[50000][50000]开不到这么大数组.
 
         问题分析:
 
         (1). 假设我们只对3堆石子a,b,c进行比较, 先合并哪2堆, 使得得分最小.
 
              score1 = (a+b) + ( (a+b)+c )
 
              score2 = (b+c) + ( (b+c)+a )
 
              再次加上score1 <= score2, 化简得: a <= c, 可以得出只要a和c的关系确定,
 
              合并的顺序也确定.
 
         (2). GarsiaWachs算法, 就是基于(1)的结论实现.找出序列中满足stone[i-1] <=
 
              stone[i+1]最小的i, 合并temp = stone[i]+stone[i-1], 接着往前面找是否
 
              有满足stone[j] > temp, 把temp值插入stone[j]的后面(数组的右边). 循环
 
              这个过程一直到只剩下一堆石子结束.
 
        (3). 为什么要将temp插入stone[j]的后面, 可以理解为(1)的情况
 
             从stone[j+1]到stone[i-2]看成一个整体 stone[mid],现在stone[j],
 
             stone[mid], temp(stone[i-1]+stone[i-1]), 情况因为temp < stone[j],
 
             因此不管怎样都是stone[mid]和temp先合并, 所以讲temp值插入stone[j]
 
             的后面是不影响结果.
再例如,
13 9 5 7 8 6 14
 首先,找第一个连续非递增区间+区间右的第一个点(不属于该区间,例如上例就是7),构成一个搜索区间。
 例如上例
第一个连续非递增区间【13 9 5】
区间右的第一个点 7
搜索区间就是【13 9 5 7】
搜索区间内取和最小的两个相邻元素。

 找到: 5 7--> 12,12不是丢在原来的位置。
 而是将它插入当前搜索区间第一个比它小的元素的上一个位置。
 就是
 13 12 9 8 6 14
 反复处理序列直到只有一个元素,接着上例,
 搜索区间【13 12 9 8 6】+【14】 = 【13 12 9 8 6 14】  
 找到 8 6 --> 14
 重新插入
 14 13 12 9 14   
 搜索区间【14 13 12 9】+【14】 = 【14 13 12 9 14】
 找到 12 9 --> 21
  
 21 14 13 14 
找到 14 13--> 27
 27 21 14
找到 21 14--> 35
 35 27
找到 35 27 --> 62
 62
 最优值 = 62+35+27+21+14+12

 

 

 1 #include <cstdio>
 2 #include <iostream>
 3 #include <cstring>
 4 using namespace std;
 5 #define MAX 50005
 6 int n;
 7 int a[MAX];
 8 int num, result;
 9 void combine(int k){
10  int i, j;
11  int temp = a[k]+a[k-1];
12  result += temp;
13  for(i = k; i < num-1; ++i)
14   a[i] = a[i+1];
15  num--;
16  for(j = k-1; j > 0 && a[j-1] < temp; --j)
17   a[j] = a[j-1];
18  a[j] = temp;
19  while(j >= 2 && a[j] >= a[j-2]){
20   int d = num-j;
21   combine(j-1);
22   j = num-d;
23  }
24 }
25 int main(){
26  int i;
27  while(scanf("%d", &n) != EOF){
28   if(n == 0) break;
29   for(i = 0; i < n; ++i)
30    scanf("%d", &a[i]);
31   num = 1;
32   result = 0;
33   for(i = 1; i < n; ++i){
34    a[num++] = a[i];
35    while(num >= 3 && a[num-3] <= a[num-1])
36     combine(num-2);
37   }
38   while(num > 1) combine(num-1);
39   printf("%d\n", result);
40  }
41  return 0;
42 }

 

posted @ 2017-08-08 14:41  浅忆~  阅读(318)  评论(0编辑  收藏  举报