回 帖 发 新 帖 刷新版面

主题:[经典的算法题分享][原创]高手是怎样练成的---30天搞定最经典的算法训练题(每天必做,不断更新)

[color=0000FF]
  由于本人最近准备全身心地投入算法学习研究中,幸运地是我买到了一本好书,里面有最经典的算法!
  特别是其中的题目,可为是经典中的“精华”,我想把其中的训练题贴上来和大家一起分享!
  我准备花一个多月的时间把它们搞定,所以我会把我做出来的题目的解答贴在后面!
  同时我也希望大家能把自己做出来了的题目的解答贴上来一起交流!争取找到更好算法!

[/color]
[code]
Algorithm Experiment Problem Set
Xiaodong Wang
(wangxd@fzu.edu.cn)
Department of Computer Science
Fuzhou University P.R.China
February, 2006
[color=FF00FF]Chapter 1 Introduction[/color]
Problem 1.1 Counting 
Problem 1.2 Dictionary
Problem 1.3 Divisors
Problem 1.4 Coin Array
Problem 1.5 Maximum Gap
[color=FF00FF]Chapter 2 Recursion and Divide and Conquer[/color]
Problem 2.1 Pipeline
Problem 2.2 Majority
Problem 2.3 Post Office
Problem 2.4 Knight’s Hamilton Tour
Problem 2.5 Half Multiset
Problem 2.6 Half Set
Problem 2.7 Soldiers
Problem 2.8 Permutation with Repetition
Problem 2.9 Lexicographic Order
Problem 2.10 Set Partition
Problem 2.11 Set Partition 2
Problem 2.12 Bicolor Towers of Hanoi
Problem 2.13 Standard 2´n Table
Problem 2.14 Integer Factorization
[color=FF00FF]Chapter 3 Dynamic Programming[/color]
Problem 3.0 Independent Task Scheduling
Problem 3.1 Coin Changing
Problem 3.2 Relation Ordering
Problem 3.3 Counting Powers
Problem 3.4 Edit Distance
Problem 3.5 Pebble Merging
Problem 3.6 Number Triangles
Problem 3.7 Multiplication Table
Problem 3.8 Renting Boats
Problem 3.9 Car Traveling
Problem 3.10 Minimal m Sums
Problem 3.11 Circle Multiplication
Problem 3.12 Maximum Cube
Problem 3.13 Regular Expressions
Problem 3.14 Bitonic Tours
Problem 3.15 Maximum k Multiplications
Problem 3.16 Cheapest Shopping
Problem 3.17 Collecting Samples
Problem 3.18 Optimal Scheduling
Problem 3.19 String Comparison
Problem 3.20 k Median of Directed Trees
Problem 3.21 k Independent Median of Directed Trees
Problem 3.22 k Median of Directed Line
Problem 3.23 2 Median of Directed Line
Problem 3.24 Maximum Connected Component of Trees
Problem 3.25 k Median of Line
Problem 3.26 k Cover of Line
Problem 3.27 m Processors
Problem 3.28 Red Nodes of Red-Black Trees
[color=FF00FF]Chapter 4 Greedy Algorithms[/color]
Problem 4.1 Lecture Halls
Problem 4.2 Optimal Merge
Problem 4.3 Program Storage
Problem 4.4 Optimal Program Storage
Problem 4.5 Program Storage
Problem 4.6 Optimal Services
Problem 4.7 Optimal Many Services
Problem 4.8 d Forest
Problem 4.9 Oiling Car
Problem 4.10 Interval Cover
Problem 4.11 Coin Changing
Problem 4.12 Delete Numbers
Problem 4.13 Maximum Sequence Differences
Problem 4.14 Nesting Boxes
Problem 4.15 Arbitrage
Problem 4.16 Booster Placement
Problem 4.17 Maximum Tape Utilization Ratio
[/code]
[quote]
[color=FF00FF]
 全身心研究数据结构与算法设计中。。。
[/color]
[/quote]

回复列表 (共110个回复)

51 楼

续...
[code]
3.动态规划方程和倒推合并过程
对子序列〔i,j〕最后一次合并,其得分为第i堆数起,顺时针数j堆的石子总数t。被合并的两堆石子是由子序列〔i,k〕和〔(i+k-1)mod n+1,j-k〕(1≤k≤j-1)经有限次合并形成的。为了求出最佳合并方案中的k值,我们定义一个动态规划方程:
当求最大得分总和时
f〔i,j〕=max{f〔i,k〕+f〔x,j-k〕+t}
1≤k≤j-1
c〔i,j〕=k│ f〔i,j〕=f〔i,k〕+f〔x,j-k〕+t
(2≤j≤n,1≤i≤n)

当求最小得分总和时
f〔i,j〕=min{f〔i,k〕+f〔x,j-k〕+t}
1≤k≤j-1
c〔i,j〕=k│ f〔i,j〕=f〔i,k〕+f〔x,j-k〕+t 
(2≤j≤n,1≤i≤n)
其中x=(i+k-1)modn+1,即第i堆数起,顺时针数k+1堆的堆序号。

例如对上面例子中的6(3 4 6 5 4 2 )堆石子,按动态规划方程顺推最小得分和。 依次得出含二堆石子的6个子序列的合并方案
f〔1,2〕=7 f〔2,2〕=10 f〔3 ,2〕=11 
c〔1,2〕=1 c〔2,2〕=1 c〔3,2〕=1
f〔4,2〕=9 f〔5,2〕=6 f〔6,2〕=5
c〔4,2〕=1 c〔5, 2〕=1 c〔6,2〕=1

含三堆石子的6(3 4 6 5 4 2 )个子序列的合并方案
f〔1,3〕=20 f〔2,3〕=25 f〔3,3〕=24 
c〔1,3〕=2 c〔2,3〕=2 c〔3,3〕=1
f〔4,3〕=17 f〔5,3〕=14 f〔6,3〕=14
c〔4,3〕=1 c〔5,3〕=1 c〔6,3〕=2

含四堆石子的6(3 4 6 5 4 2 )个子序列的合并方案
f〔1,4〕=36 f〔2,4〕=38 f〔3,4〕=34
c〔1,4〕=2 c〔2,4〕=2 c〔3,4〕=1
f〔4,4〕=28 f〔5,4〕=26 f〔6,4〕=29
c〔4,4〕=1 c〔5,4〕=2 c〔6,4〕=3

含五堆石子的6(3 4 6 5 4 2 )个子序列的合并方案
f〔1,5〕=51 f〔2,5〕=48 f〔3,5〕=45 
c〔1,5〕=3 c〔2,5〕=2 c〔3,5〕=2 
f〔4,5〕=41 f〔5,5〕=43 f〔6,5〕=45
c〔4,5〕=2 c〔5,5〕=3 c〔6,5〕=3

含六堆石子的6(3 4 6 5 4 2 )个子序列的合并方案
f〔1,6〕=61 f〔2,6〕=62 f〔3,6〕=61 
c〔1,6〕=3 c〔2,6〕=2 c〔3,6〕=2 
f〔4,6〕=61 f〔5,6〕=61 f〔6,6〕=62
c〔4,6〕=3 c〔5,6〕=4 c〔6,6〕=3

f〔1,6〕是f〔1,6〕,f〔2,6〕,……f〔6,6〕中的最小值,表明最小得分和是由序列〔1,6〕经5次合并得出的。我们从这个序列出发, 按下述方法倒推合并过程:
由c〔1,6〕=3可知,第5次合并的两堆石子分别由子序列〔1,3〕和子序列〔4,3〕经4次合并后得出。其中c〔1,3〕=2可知由子序列〔1,3〕合并成的一堆石子是由子序列〔1,2〕和第三堆合并而来的。而c〔1,2〕=1,以表明了子序列〔1,2〕的合并方案是第1堆合并第2堆。
由此倒推回去,得出第1,第2次合并的方案,每次合并得分 
第一次合并 3 4 6……    ->7
第二次合并 7 6……       ->13
13…… 
子序列〔1,3〕经2次合并后合并成1堆, 2次合并的得分和=7+13=20。
c〔4,3〕=1,可知由子序列〔4,3〕合并成的一堆石子是由第4堆和子序列〔5,
2〕合并而来的。而c〔5,2〕=1,又表明了子序列〔5,2〕的合并方案是第5堆合并第6堆。由此倒推回去,得出第3、第4次合并的方案 
每次合并得分:
第三次合并 ……54 2   ->6 
第四次合并 ……5 6     ->11 
……11 
子序列〔4,3〕经2次合并后合并成1堆,2次合并的得分和=6+11=17。
第五次合并是将最后两堆合并成1堆,该次合并的得分为24。
显然,上述5次合并的得分总和为最小
20+17+24=61

上述倒推过程,可由一个print(〔子序列〕)的递归算法描述
procedure print (〔i,j〕)
begin
if j〈〉1 then {继续倒推合并过程
begin 
print(〔i,c〔i,j〕〕;{倒推子序列1的合并过程}
print(〔i+c〔i,j〕-1〕mod n+1,j-c〔i,j〕)
{倒推子序列2的合并过程}
for K:=1 to N do{输出当前被合并的两堆石子}
if (第K堆石子未从圈内去除)
then begin
if(K=i)or(K=X)then置第K堆石子待合并标志
else第K堆石子未被合并;
end;{then}
第i堆石子数←第i堆石子数+第X堆石子数;
将第X堆石子从圈内去除;
end;{then}
end;{print}
例如,调用print(〔1,6〕)后的结果如下:
              print(〔1,6〕)⑤
                ┌──────┴──────┐
          print(〔1,3〕)②           print(〔4,3〕)④
      ┌─────┴─────┐           ┌─────┴─────┐
  print(〔1,2〕)① print(〔3,1〕)   print(〔4,1〕) print(〔5,2〕)③
┌──────┴──────┐                 ┌──────┴──────┐         
print(〔1,1〕)     print(〔2,1〕)           print(〔5,1〕)       print(〔6,1〕) 
(图6.2-5)
其中回溯至
① 显示 3 46 5 4 
② 显示 7 65 4 2 
③ 显示 13 54 2
④ 显示 135 6
⑤ 显示 13 11
注:调用print过程后,应显示6堆石子的总数作为第5次合并的得分。
[/code]

52 楼

续...
[code]

Program Stones;
Type
Node = Record{当前序列的合并方案}
c : Longint;{得分和}
d : Byte{子序列1的堆数}
End;
SumType = Array [1..100,1..100] of Longint;
{sumtype[i,j]-子序列[i,j]的石子总数}
Var
List : Array [1..100,1..100] of Node;
{list[i,j]-子序列[i,j]的合并方案}
Date, Dt : Array [1..100] of Integer;
{Date[i]-第i堆石子数,Dt-暂存Date}
Sum : ^SumType;{sum^[i,j]-指向子序列[i,j]的石子总数的指针}
F : Text;{文件变量}
Fn : String;{文件名串}
N, i, j : Integer;{N-石子堆数,i,j-循环变量}

Procedure Print(i, j : Byte);{递归打印子序列[i,j]的合并过程}
Var
k, x : Shortint;{k-循环变量;x-子序列2中首堆石子的序号}
Begin
If j <> 1 Then Begin{继续倒推合并过程}
Print(i, List[i,j].d);{倒推子序列1的合并过程}
x := (i + List[i, j].d - 1) Mod N + 1;{求子序列2中首堆石子的序号}
Print(x, j - List[i, j].d);{倒推子序列2的合并过程}
For k := 1 to N Do{输出当前合并第i堆,第x堆石子的方案}
  If Date[k] > 0 Then Begin
    If (i= k)or(x=k)Then Write(F, - Date[k], ' ')
    Else Write(F, Date[k], ' ')
  End; { Then }
Writeln(F);{输出换行符}
Date[i] := Date[i] + Date[x];{原第i堆和第x堆合并成第i堆}
Date[x] := - Date[x]{将原第x堆从圈内去除}
End { Then }
End; { Print }

Procedure Main(s : Shortint);
Var
i, j, k : Integer;
t, x : Longint;
Begin
For i := 1 to N Do Begin{仅含一堆石子的序列不存在合并}
  List[i, 1].c := 0;
  List[i, 1].d := 0
End; {For}
For j := 2 to N Do{顺推含2堆,含3堆……含N堆石子的各子序列的合并方案}
  For i := 1 to N Do Begin{当前考虑从第i堆数起,顺时针数j堆的子序列}
    If s = 1 Then List[i, j].c := Maxlongint{合并[i,j]子序列的得分和初始化}
    Else List[i, j].c := 0;
    t := Sum^[i, j];{最后一次合并的得分为[i,j]子序列的石子总数}
    For k := 1 to j - 1 Do Begin{子序列1的石子堆数依次考虑1堆……j-1堆}
    x := (i + k - 1) Mod N + 1;{求子序列2首堆序号}
    If (s=1) And (List[i,k].c + List[x,j-k].c+t < List[i, j].c)
Or (s=2) And (List[i,k].c + List[x,j-k].c+t > List[i, j].c)
{ 若该合并方案为目前最佳,则记下}
    Then Begin
      List[i, j].c := List[i, k].c + List[x, j - k].c + t;
      List[i, j].d := k
    End { Then }
    End { For }
End; { For }
{在子序列[1,N],[2,N],……,[N, N]中选择得分总和最小(或最大)的一个子序列}
k := 1; x := List[1, N].c;
For i := 2 to N Do
  If (s = 1) And (List[i, N].c < x) Or (s = 2) And
(List[i, N].c > x) Then Begin
    k := i; x := List[i, N].c
  End; { Then }
Print(k, N);{由此出发,倒推合并过程}
Writeln(F, Sum^[1, N]);{输出最后一次将石子合并成一堆的石子总数}
Writeln(F);
Writeln(list[k, N].c)
End; { Main }

Begin
Write( 'File name = ');{输入文件名串}
Readln(Fn);
Assign(F, Fn);{该文件名串与文件变量连接}
Reset(F);{文件读准备}
Readln(F, N);{读入石子堆数}
For i := 1 to N Do Read(F, Date[i]);{读入每堆石子数}
New(Sum);{求每一个子序列的石子数sum}
For i := 1 to N Do Sum^[i, 1] := Date[i];
For j := 2 to N Do
  For i := 1 to N Do
    Sum^[i, j] := Date[i] + Sum^[i Mod N + 1, j - 1];
Dt := Date;{暂存合并前的各堆石子,结构相同的变量可相互赋值}
Close(F);{关闭输入文件}
Assign(F, 'OUTPUT.TXT ');{文件变量与输出文件名串连接}
Rewrite(F);{文件写准备}
Main(1);{求得分和最小的合并方案}
Date := Dt;{恢复合并前的各堆石子}
Main(2);{求得分和最大的合并方案}
Close(F){关闭输出文件}
End.
[/code]

53 楼


如果a[i]表示从0堆加到i堆的石子数的话
那好像应该是这样:
if ( i == 0 )
    sum = a[j];  //因为k < n, 所以j = 0 + k < n, 所以不用取j % n
else
    sum = ( j / n ) * a[n - 1] + a[j % n] - a[i - 1];
//sum表示从第i堆到第j堆石子数的和

还有一点i是小于n的不会出现i == n的状况

54 楼

[quote]
如果a[i]表示从0堆加到i堆的石子数的话
那好像应该是这样:
if ( i == 0 )
    sum = a[j];  //因为k < n, 所以j = 0 + k < n, 所以不用取j % n
else
    sum = ( j / n ) * a[n - 1] + a[j % n] - a[i - 1];
//sum表示从第i堆到第j堆石子数的和

还有一点i是小于n的不会出现i == n的状况[/quote]

谢谢。。。

改为:
[code]
  
        
        for( k = 1,fo=2*n-3 ; k < n ; k ++,fo-- )
        for( i = 0 ; i <= fo ; i ++ )
        {
             j =  i + k ;
            
            
            
            max = 0 ;
            min = INF ;
            if ( i == 0 || i == n)
                        sum = a[j%n] ;
                        else
                        sum = a[j%n]-a[(i-1)%n];
            
           
[/code]

但是我发现书中给的测试数据有误。。。

55 楼

[quote]
这么网站有更多的算法源码
http://www.sfcode.cn[/quote]

7 8 错...

56 楼

十几天了,做了一些题,但是本人水平不高,有些问题还是不能解决,但还是有兴趣的,希望作者能把答案和剩下的内容发上去,把整本书发上去更好 谢谢楼主了

57 楼

谢谢了哦,,哈哈,,,[em12]

58 楼

书的名字叫什么

59 楼

[quote]十几天了,做了一些题,但是本人水平不高,有些问题还是不能解决,但还是有兴趣的,希望作者能把答案和剩下的内容发上去,把整本书发上去更好 谢谢楼主了[/quote]

[color=FF00FF]很抱歉!!!
最近忙于做论坛,所以没时间,不过再过几天,我会弄上来的......[/color]

60 楼

谢谢楼主,期待着,以后有不懂的希望楼主帮我们或者大家一起解决哦

我来回复

您尚未登录,请登录后再回复。点此登录或注册