關於動態規划算法的總結


動態規划算法。在T大某位老師的書中說就是遞推+反復子問題。

動態規划算法的效率主要與反復子問題的處理有關。

 

典型的題目有 陪審團。最大公共子串問題

 

1,最大公共子串問題

 

這個是動態規划的基礎題目。

動態規划就是遞推和反復子結構。

確定了遞推關系后。找到一個能極大地降低反復運算的子結構至關重要。選的好了,時間效率會非常好。

這個問題,最好還是設第一個串為a,長度為n,第二個串為b。長度m。那么最長的子序列長度為f(n,m)

當a[n]=a[m]時

f(n,m)=1+f(n-1,m-1)

否則f(n,m)=max(f(n-1),f(m-1))

同一時候建立一個存儲計算過的f(x,y)的矩陣,假設計算過了就直接使用

程序例如以下所看到的

 

[cpp]  view plain copy
  1. #include <iostream>  
  2. #define MAXLen 1000  
  3. char seq1[MAXLen];  
  4. char seq2[MAXLen];  
  5. int maxLen[MAXLen][MAXLen];  
  6. int MaxCommonMain()  
  7. {  
  8.     while(scanf("%s%s",seq1+1,seq2+1)>0)  
  9.     {  
  10.       
  11.     int length1=strlen(seq1+1);  
  12.     int length2=strlen(seq2+1);  
  13.       
  14.       
  15.     for(int i=0;i<=length1;i++)  
  16.       maxLen[i][0]=0;  
  17.     for(int j=0;j<=length2;j++)  
  18.       maxLen[0][j]=0;  
  19.         
  20.       
  21.         
  22.         
  23.     for(int i=1;i<=length1;i++)  
  24.     {  
  25.       for(int j=1;j<=length2;j++)  
  26.       {  
  27.        if(seq1[i]==seq2[j])  
  28.          maxLen[i][j]=maxLen[i-1][j-1]+1;  
  29.        else  
  30.          maxLen[i][j]=maxLen[i-1][j]>maxLen[i][j-1]?maxLen[i-1][j]:maxLen[i][j-1];  
  31.       }  
  32.     }  
  33.       
  34.     printf("%d/n",maxLen[length1][length2]);  
  35.       
  36.     // printf("%d",maxLen[length2-1][length1-1]);  
  37.       
  38. }  
  39.     return 0;  
  40. }  
 

 

2,陪審團問題

問題描寫敘述:

問題描寫敘述
在遙遠的國家佛羅布尼亞。嫌犯是否有罪,須由陪審團決定。陪審團是由法官從公眾中
挑選的。先隨機挑選n 個人作為陪審團的候選人。然后再從這n 個人中選m 人組成陪審團。


選m 人的辦法是:
控方和辯方會依據對候選人的喜歡程度,給全部候選人打分。分值從0 到20。

為了公
平起見。法官選出陪審團的原則是:選出的m 個人,必須滿足辯方總分和控方總分的差的
絕對值最小。假設有多種選擇方案的辯方總分和控方總分的之差的絕對值同樣。那么選辯控
兩方總分之和最大的方案就可以。終於選出的方案稱為陪審團方案。


輸入數據
輸入包括多組數據。每組數據的第一行是兩個整數n 和m,n 是候選人數目。m 是陪審
團人數。

注意,1<=n<=200, 1<=m<=20 並且 m<=n。

接下來的n 行。每行表示一個候選人
的信息。它包括2 個整數。先后是控方和辯方對該候選人的打分。

候選人按出現的先后從1
開始編號。

兩組有效數據之間以空行分隔。最后一組數據n=m=0
輸出要求
對每組數據,先輸出一行。表示答案所屬的組號, 如 'Jury #1', 'Jury #2', 等。

接下來的
一行要象樣例那樣輸出陪審團的控方總分和辯方總分。

再下來一行要以升序輸出陪審團里每
個成員的編號,兩個成員編號之間用空格分隔。每組輸出數據須以一個空行結束。
輸入例子
4 2
1 2
2 3
4 1
6 2
0 0
輸出例子
Jury #1
Best jury has value 6 for prosecution and value 4 for defence:
2 3

 

用動態規划來解

動態規划能夠看成是反復子問題+遞歸

設p為控方向量,d為辯方向量

評分從-400到400
f(j,k)為選取第j個人,評分差為k的辯控總分
對於隨意的i,i大於0小於n,則有
f(j,k)=f(j-1+1,x+p[i]-d[i])
則就有
f(j,k)=f(j-1,x)+p[i]+d[i];

假設新的f(j,k)大於舊的,則替換舊的。否則不替換

初始值為k=0。在程序中也許是某個值,以免避免序號k出現負值。
那么初始的f(0,0)=0。
在第一輪的時候僅僅有辯控差為0的合理
對全部的候選人做
f(1,p[i]-d[i])=max(f(1,p[i]-d[i]),f(0,0)+p[i]+d[i]);
這樣就能夠完畢選一個人的操作。


做完之后離k=0近期的非負的就是我們要的最大的辯控和。

下標就是辯控差。


同理,假設選兩個的話,第二輪
k=-400~400
假設f(1,k)合理(此時,僅僅有在第一輪中算出來的合理)
f(2。k+p[i]-d[i])=max(f(2,k+p[i]-d[i]),f(1,k)+p[i]+d[i]);

假設要保存搜索得到的路徑
設路徑path保存路徑向量
path(j,k)保存推薦第j個陪審員時候,辯控差為k的候選人。


相應上面的f函數。初始值都設為0。


假設僅僅找一個候選人
則本身path中沒有不論什么的值。即沒有推薦不論什么的人。也就是說,推薦第一個人的時候。誰都能夠(若推薦第二個人。則曾經推薦過的不能再次推薦)
在推薦第一個候選人的時候。僅僅管保存path[0][p[i]-d[i]]=i ;

當選取多個候選人的時候,在更新f(j,k)的同一時候更新path。由於更新f(j,k)的同一時候。意味着選了新的候選人。所以須要更新路徑


當以上都做完之后,
定義一個數組result來取路徑
對i:1~m
 result[i]=path[m-i+1][k];
 k=k-p[result[i]]+d[result[i]];
這樣就都攻克了。

[cpp]  view plain copy
  1. #include<stdlib.h>  
  2. #include<iostream>  
  3. #include<memory.h>  
  4. int p[300];  
  5. int d[300];  
  6. int path[30][1000];  
  7. int f[30][1000];  
  8. int result[30];  
  9. int compareInt(const void *el, const void *e2)  
  10. {  
  11.     return *((int *)el)-*((int *)e2);  
  12. }  
  13. int jurymain()  
  14. {  
  15.     int n,m;  
  16.     scanf("%d%d",&n,&m);  
  17.        int no=0;  
  18.        int i=0;  
  19.     while(n+m)//n=0,m=0結束條件   
  20.     {  
  21.     no++;  
  22.       for(i=1;i<=n;i++)  
  23.         scanf("%d%d",&p[i],&d[i]);  
  24.       memset(f,-1,sizeof(f));   
  25.       memset(path,0,sizeof(path));  
  26.       int minD=20*m;  
  27.       f[0][minD]=0;  
  28.       for(int j=0;j<m;j++)  
  29.       {        
  30.        for(int k=0;k<=minD*2;k++)  
  31.        {  
  32.                  if(f[j][k]>=0)  
  33.                 {  
  34.                               i=1;  
  35.                               int temp1;  
  36.                               int temp2;  
  37.                               for(;i<=n;i++)  
  38.                               {  
  39.                                if(f[j][k]+p[i]+d[i]>f[j+1][k+p[i]-d[i]])  
  40.                                {  
  41.                                   temp1=j;  
  42.                                   temp2=k;  
  43.                                   while(temp1>0&&path[temp1][temp2]!=i)  
  44.                                   {   
  45.                                                                          
  46.                                     temp2=temp2-p[path[temp1][temp2]]+d[path[temp1][temp2]];  
  47.                                     temp1--;  
  48.                                      
  49.                                   }   
  50.                               if(temp1==0)  
  51.                               {  
  52.                                f[j+1][k+p[i]-d[i]]=f[j][k]+p[i]+d[i];  
  53.                                path[j+1][k+p[i]-d[i]]=i;  
  54.                               }  
  55.                            }   
  56.                                 
  57.                               
  58.                  }  
  59.                }  
  60.        }  
  61.       }  
  62.      i=minD;  
  63.      int j=0;  
  64.      int k=0;  
  65.       while(f[m][i+j]<0&&f[m][i-j]<0)  
  66.        j++;  
  67.        if(f[m][i+j]>f[m][i-j])  
  68.        k=i+j;  
  69.        else  
  70.        k=i-j;  
  71.          
  72.        printf("Jury #%d/n",no);  
  73.       printf("Best jury has value %d for prosecution and value %d for defence:/n",(k-minD+f[m][k])/2,(minD-k+f[m][k])/2);  
  74.       
  75.         
  76.       for(i=1;i<=m;i++)  
  77.       {  
  78.               result[i]=path[m-i+1][k];  
  79.               k-=p[result[i]]-d[result[i]];  
  80.             //  std::cout<<"result "<<i<<" "<<result[i]<<std::endl;  
  81.       }  
  82.       qsort(result+1,m,sizeof(int),compareInt);  
  83.       for(i=1;i<=m;i++)  
  84.       printf("%d ",result[i]);  
  85.       printf("/n/n");  
  86.       scanf("%d%d",&n,&m);  
  87.         
  88.     }  
  89.     //system("pause");  
  90.     return 0;  
  91. }  
 

3,小花店問題

F束花從左到右放在V個花瓶里面(1<=F<=V<=100)。花要依照花的標志數從小到大排列。不同的花在不同的花瓶可以產生不同的美學價值。v[i,j]來表示第i朵花在第j個花瓶產生的美學價值。

求把n朵花放在m個花瓶可以產生的最大的美學價值。

這個題和最大公共子串的思考角度相似。因為花必需要小於等於瓶子。並且花的編號由小到大。不能亂序。

比如就不能把出現【2,4】 【1,5】這樣的現象。也就是說插在花瓶中的花依照花瓶的順序。序號升序排列。

  最好還是用f(i,j)來表示把前i朵花插入前個瓶子中。當i=j時,f(i,j)=v[1,1]+v[2,2]+...+v[i,i];

當i=0時。f(0,j)=0; 當i!=j, 且i!=0時f(i,j)=max(f(i,j-1),f(i-1,j-1)+v[i,j]) ,i<=j-1.

[cpp]  view plain copy
  1. #include <stdlib.h>  
  2. #include <stdio.h>  
  3. void getMaxValue(int **f,int **v,int m,int n)  
  4. {  
  5.        
  6.        
  7.        
  8.        
  9.     for(int j=1;j<=m;j++)  
  10.     {  
  11.             for(int i=1;i<=j-1;i++)  
  12.             {  
  13.               if(f[i-1][j-1]+v[i][j]>f[i][j-1])  
  14.                  f[i][j]=f[i-1][j-1]+v[i][j];  
  15.               else  
  16.                  f[i][j]=f[i][j-1];  
  17.             }  
  18.     }  
  19.       
  20.       
  21. }  
  22. int main()  
  23. {  
  24.     int n,m;  
  25.     scanf("%d%d",&n,&m);  
  26.     int **v=new int[n+1][m+1];  
  27.     int **f=new int[n+1][m+1];  
  28.     v[0][0]=0;  
  29.     for(int i=1;i<=n;i++)  
  30.      for(int j=1;j<=m;j++)  
  31.     scanf("$d",v[i][j]);  
  32.       
  33.     for(int j=0;j<=m;j++)  
  34.     v[0][j]=0;  
  35.       
  36.     int tempSum=0;  
  37.     for(int i=0;i<=n;i++)  
  38.     {  
  39.             sum+=v[i][i];  
  40.             f[i][i]=sum;  
  41.     }  
  42.     getMaxValue(f,v,m,n);  
  43.        
  44.     delete(v);  
  45.     return 0;  
  46.       
  47. }  

 

 4,最佳旅行線問題

簡單描寫敘述:某航空公司給的獎品。一張免費的機票。可以從最西邊的一個城市出發,必須到達最東邊的一個城市,然后返回起點城市。每一個城市職能經過一次。起點被訪問2次。問最多可以訪問多少個城市。

這個題能夠使用動態規划的方法。這個題目也使我想起了《算法導論》中裝配線的問題,事實上基本是一致的。

在非常多的書或者是解法中。都用了這個方案:從起點開始,兩條路一起走f[i,j]表示兩條不相交的路線分別到達i和j時,所需乘坐航線的最大值

f(1,1)=0;

f(i,j)= max{f(k,j)}+1,i>j ;k是i的前驅節點。

         max{f(i,k)}+1,i<j

f(i,i)無意義

該算法是沒有錯的。

非常多地方也用這個算法做了一些程序版本號。可是這個算法有一個限制。就是前驅節點。在t大某位老師的書中,他枚舉全部的點對。然后對f矩陣進行更新(傳統的動態規划方法)。可是他這個有一個前提,那就是全部節點的序號符合拓撲序列。可是題目中並沒有說城市符合拓撲序列。假如說序號比較任意,t大的這本書中的就錯了。可是他並沒有在代碼中說明。算是對讀者的一個誤導。

[cpp]  view plain copy
  1. #ifndef TICKETAWARD_H  
  2. #define TICKETAWARD_H  
  3. class TicketAward  
  4. {  
  5. int *b;//度數  
  6. int **g;//g(i,j)表示第i個點的第j個孩子  
  7. int **f;//函數f  
  8. int n;//總共同擁有n個節點  
  9. public:  
  10.     TicketAward();  
  11.     int max(int x,int y);  
  12. };  
  13. #endif  
 

[cpp]  view plain copy
  1. #include "stdafx.h"  
  2. #include <iostream>  
  3. #include "TicketAward.h"  
  4. TicketAward::TicketAward()  
  5. {  
  6.     std::cin>>n;  
  7.     g=new int *[n+1];  
  8.     b=new int[n+1];  
  9.     f=new int*[n+1];  
  10.     for(int i=1;i<=n;i++)  
  11.     {  
  12.         g[i]=new int[n+1];  
  13.         f[i]=new int[n+1];  
  14.         b[i]=0;  
  15.         for(int j=1;j<=n;j++)  
  16.         {  
  17.               
  18.         }  
  19.     }  
  20.     int temp=0;  
  21.     for(int i=1;i<=n;i++)  
  22.     {  
  23.         std::cin>>temp;//輸入i的第一個孩子。假設不為0,即就是存在孩子節點,則繼續  
  24.         while(temp!=0)  
  25.         {  
  26.             b[i]++;  
  27.             g[i][b[i]]=temp;  
  28.             std::cin>>temp;  
  29.         }  
  30.     }  
  31.     f[1][1]=0;  
  32.     for(int i=1;i<=n;i++)  
  33.     {  
  34.       
  35.         for(int j=1;j<=b[i];j++)  
  36.         {  
  37.             if(i==j==1)  
  38.                 continue;  
  39.             if(i<j)  
  40.             for(int k=1;k<=b[i];k++)  
  41.             {  
  42.                 f[i][j]=max(f[i][j],(f[g[i][k]][j]+1));  
  43.             }  
  44.             if(i>j)  
  45.             for(int k=1;k<=b[j];k++)  
  46.             {  
  47.                 f[i][j]=max(f[i][j],(f[g[j][k]][j]+1));  
  48.             }  
  49.         }  
  50.     }  
  51.         std::cout<<(f[n][n]-2);  
  52. }  
  53. int TicketAward::max(int x, int y)  
  54. {  
  55.     return x>y?

    x:y;  

  56. }  
 

 

 

5, 最長詞鏈問題

給定一個只包括小寫字母的英文單詞表。當中每一個單詞至少包括一個字母,最多包括75個字母。且依照字典序排列。

全部單詞包括的單詞個數之和不超過200w

     假設在一張由一個單詞或者多個單詞組成的表中。每一個單詞都被其后的一個單詞所包括。則成此表為一個鏈。

     如今要求從單詞表中找到一個包括單詞書最多的最長詞鏈。

該問題能夠由動態規划解。

這讓我想起了一個題:在一個連續的長字符串中。找出匹配次數最多的一個詞組,不能有重疊。同樣的方法。

令F(k)表示第k個單詞的時候的最長的鏈。

則F(k)=maxF(i)+1。i從1,到k-1,而且第i個單詞是第k個的前綴。這兒問題就解了。

可是,對於這道題還有其它的解法。

由於字典序並不是亂序。每一個單詞的前一個單詞所包括的前綴非常可能就是這個單詞的前綴,而且前一個單詞也可能就是該單詞的前綴。由於前綴的特殊性。所以僅僅用檢查前一個單詞的前綴(包括該單詞本身)就能找出該前綴鏈

代碼為非動態規划算法

[cpp]  view plain copy
  1. #include "stdafx.h"  
  2. #ifndef MOSTLENGTHLETTER_H  
  3. #define MOSTLENGTHLETTER_H  
  4. #include <iostream>  
  5. #include <string>  
  6. struct Pnode  
  7. {  
  8.     int len;  
  9.     Pnode *next[26];  
  10. };  
  11. struct MostLengthLetters  
  12. {  
  13.        public:  
  14.            MostLengthLetters()  
  15.        {  
  16.          for(int i=0;i<=25;i++)  
  17.          {  
  18.              root.next[i]=NULL;  
  19.          }  
  20.          ans=0;  
  21.          std::cout<<"please input the num of words"<<std::endl;  
  22.          std::cin>>wordsNum;  
  23.           
  24.          this->getWords();  
  25.            
  26.        }  
  27.        void getWords();  
  28.        void update(std::string temp);  
  29.        int wordsNum;  
  30.        Pnode root;  
  31.        int ans;  
  32. };  
  33. void MostLengthLetters::getWords()  
  34. {  
  35.     std::string temp;  
  36.     for(int i=0;i<this->wordsNum;i++)  
  37.     {  
  38.         std::cin>>temp;  
  39.         this->update(temp);  
  40.     }  
  41. }  
  42. void MostLengthLetters::update(std::string temp)  
  43. {  
  44.     int len=temp.length();  
  45.     Pnode p=root;  
  46.     int max=0;  
  47.     int i=1;  
  48.     int tempInt=0;  
  49.     while(i<len)  
  50.     {  
  51.         tempInt=temp[i]-'a';  
  52.         if(p.next[i]==NULL)  
  53.         {  
  54.             Pnode *node=new Pnode;  
  55.             node->len=0;  
  56.             for(int j=0;j<=25;j++)  
  57.             {  
  58.                 node->next[j]=NULL;  
  59.             }  
  60.         }  
  61.         if(max<p.len)  
  62.             max=p.len;  
  63.         i++;  
  64.         p=*p.next[tempInt];  
  65.     }  
  66.     p.len=max+1;  
  67.     this->ans=p.len;  
  68.     return;  
  69. }  
  70. #endif  

6, 整數划分問題

給定一個自然數。分成k部分,A1,A2..的數的和,要求A1<=A2...求有多少種?

 

 

原理:整數n拆分成最多不超過m個數的和的拆分數,和n 拆分成最大不超過m的拆分數相等。

依據這個原理,原問題就轉化成了求最大拆分為k的拆分個數與最大拆分為k-1的拆分個數的差
f(n,k)=f(n,k-1)+f(n-k,k)。
[cpp]  view plain copy
  1. #include "stdafx.h"  
  2. #ifndef SPLITTOKNUM_H  
  3. #define SPLITTOKNUM_H  
  4. #include<iostream>  
  5. #include <memory.h>  
  6. #define MaxN 100  
  7. class SplitToKNum  
  8. {  
  9.       
  10. public:  
  11.     SplitToKNum()  
  12.     {  
  13.         std::cin>>n;  
  14.         std::cin>>k;  
  15.         memset(f,0,sizeof(f));  
  16.         for(int i=1;i<=k;i++)  
  17.         {  
  18.             f[MaxN][i]=1;  
  19.         }  
  20.         for(int j=1;j<=k;j++)  
  21.             for(int i=MaxN+1;i<=MaxN+n;i++)  
  22.                 f[i][j]=f[i][j-1]+f[i-j][j];  
  23.         std::cout<<f[n+MaxN][k]-f[n+MaxN][k-1]<<std::endl;  
  24.           
  25.     }  
  26.     int n;  
  27.     int k;  
  28.     int f[2*MaxN+1][MaxN];  
  29. };  
  30. #endif  
 
同一時候附上ferrer圖的幾個原理:
a,整數n拆分成k個數的和的拆分數,和數n拆分最大數位k的拆分數相等
b。整數n拆分成最多不超過m個數的和的拆分數。和n拆分成最大不超過m的拆分數相等。
c。整數n拆分成互不同樣的若干奇數的和的的拆分數,和n拆分成自共軛的Ferrers圖像的拆分數相等.

7,青蛙的煩惱

池塘里面有n片荷花,正好形成一個凸多邊形。依照順時針方向將這n片和也順次編號為1,2,3,4,5...。n。一僅僅青蛙想要跳過這些荷葉,每一個僅僅能跳過一次。希望跳過的距離最短。

輸入荷葉數n。每片荷葉的坐標xy,輸出最短距離。

分析:凸多邊形。首先不能出現交叉路。否則。由兩邊之和大於第三邊能夠知道,這樣實際上是路程變長了。

其次,直接依照凸多邊形也不對,能夠參考平行四邊形,當中一個角為30度,請自行繪圖查明。

 

方法:每次每一個頂點僅僅能到與自己相鄰的兩個中的一個(到其它頂點邊會出現交叉),剩下的n-1個頂點也遵從這一規則。則能夠使用動態規划的方法。

 

從s到L

則假設從s出發,則f(s,L,0)=min{dis(s。s+1)+f(s+1,L-1,0),f(s+1,L-1,1)+dis(s+L-1,s)}

假設從s+L-1出發則f(s,L,1)=min{dis(s,s+L-1)+f(s+L-1,L-1,0),f(s+L-1,L-1,0)+dis(s+L-1,s)}

[cpp]  view plain copy
  1. #include <stdlib.h>  
  2. #include <stdio.h>  
  3. #include <iostream>  
  4. #define MAXN 1000  
  5. double x[MAXN];// x radix  
  6. double y[MAXN];  
  7. double dis[MAXN][MAXN];  
  8. int n;// the num of   
  9. double f[MAXN][2][2];  
  10. double min(double a,double b)  
  11. {  
  12.  return a<b?

    a:b;  

  13. }  
  14. int main()  
  15. {  
  16.     std::cout<<"please input the num of the points"<<std::endl;  
  17.     std::cin>>n;  
  18.     for(int i=0;i<n;i++)  
  19.     {  
  20.         std::cin>>x[i]>>y[i];  
  21.     }  
  22.     for(int i=0;i<n-1;i++)  
  23.     {  
  24.         for(int j=i+1;j<n;j++)  
  25.         {  
  26.           dis[i][j]=(x[i]-x[j])*(x[i]-x[j])+(y[i]-y[j])*(y[i]-y[j]);  
  27.           dis[j][i]=dis[i][j];  
  28.         }  
  29.     }  
  30.     int sign=0;  
  31.     double ans=1000000;  
  32.     for(int j=1;j<=n;j++)  
  33.     {  
  34.      sign=1-sign;  
  35.      for(int i=n-1;i>=0;i--)  
  36.      {  
  37.         if(j==1)  
  38.         {  
  39.         f[i][sign][0]=0;  
  40.         f[i][sign][1]=0;  
  41.         }  
  42.             else  
  43.         {  
  44.          f[i][sign][1]=min(f[(i+1)%n][1-sign][1]+dis[i][(i+1)%n],f[(i+1)%n][1-sign][0]+dis[i][(i+j-1)%n]);  
  45.          f[i][sign][0]=min(f[i][1-sign][1]+dis[(i+j-1)%n][i],f[i][1-sign][0]+dis[(i+j-1)%n][(i+j-2)%n]);      
  46.         }  
  47.         if(j==n)  
  48.         ans=min(ans,f[i][sign][1]);  
  49.      }  
  50.     }  
  51.     //std::cout<<f[1][sign][1]<<"/t"<<f[1][sign][0]<<std::endl;  
  52.     std::cout<<ans<<std::endl;  
  53.       
  54.     return 0;  
  55. }  

 

8排列問題

 

在整數1,2,。。。,n中,有些排列滿足以下的性質:該排列中除了最后一個數字以外,每一個整數后面都跟有一個同它相差為1的整數。比如:N=4,排列1432滿足上述性質。求,假設 隨意指定p個位置的值,求最多有幾個排列滿足如上性質

 

這樣的排列滿足一下兩條性質

a,不論什么一個排列的后k位是若干連續整數的集合

b,假設一個排列的后k位(1<=k<=n)是軟干連續整數組成的集合,則這個排列滿足題目所述的性質。

 

動態規划的方法就可以。

設s為起始的數的大小。r為連續的多少數的個數

 

g(s,r)=g(s+1,j-1),若第1個位置為s。即倒數第j個位置為s

   g(s,j-1),若第1個位置為s+r-1,即倒數的第j個位置為s+r-1

   不確定則為上述兩個之和

 

代碼例如以下

[cpp]  view plain copy
  1. #include <stdlib.h>  
  2. #include <iostream>  
  3. #include <memory.h>  
  4. class RankProgram  
  5. {  
  6. public:  
  7.         RankProgram()  
  8.         {  
  9.          std::cout<<"please input the testing data"<<std::endl;  
  10.          std::cin>>n>>p;  
  11.          s=new int[n+2];  
  12.          g=new int* [n+2];  
  13.          for(int i=0;i<=n+1;i++)  
  14.          {  
  15.           g[i]=new int[n+2];  
  16.          memset(g[i],0,sizeof(int)*(n+2));  
  17.          g[i][1]=1;  
  18.          }  
  19.          memset(s,0,sizeof(int)*(n+2));  
  20.          int tempData;  
  21.          int tempP;  
  22.         for(int i=1;i<=p;i++)  
  23.         {  
  24.          std::cin>>tempP>>tempData;  
  25.          s[n-tempP+1]=tempData;  
  26.         }  
  27.         for(int i=n;i>=1;i--)  
  28.         {  
  29.          for(int j=2;j<=n-i+1;j++)  
  30.         {  
  31.            if(s[j]==i)  
  32.                 g[i][j]=g[i+1][j-1];  
  33.            else if(s[j]==(i+j-1))  
  34.                 g[i][j]=g[i][j-1];  
  35.            else   
  36.                 g[i][j]=g[i+1][j-1]+g[i][j-1];  
  37.         }  
  38.         }  
  39.          std::cout<<g[1][n]<<std::endl;  
  40.         }  
  41.         ~RankProgram()  
  42.         {  
  43.          delete [] s;  
  44.         for(int i=0;i<=n+1;i++)  
  45.         delete [] g[i];  
  46.         delete [] g;  
  47.         }  
  48. private:  
  49.         int *s;  
  50.         int **g;  
  51.         int p;  
  52.         int n;  
  53. };  
  54. int main()  
  55. {  
  56.  RankProgram rp;  
  57. }  
 

9,畫室問題

 

實際上該題能夠描寫敘述為。給定一個尺寸為N的矩陣,行和列寬度為2^N,將該矩陣分為4分,左上角不為零,其它三部分都包括零。

遞歸定義這三部分,直到最小單位,即矩陣僅僅有一個數,為0。

求假設讓該矩陣移動一個尺寸(x,y),兩個矩陣反復部分的0的個數。

 

思考:實際上就是推斷移動前和移動后相應的位置是否都為0。假設都為0,則總0個數+1。

假設移動到了左上角的位置則不加。其它三部分由於是一樣的,該部分遞歸定義的上層+1

 

在以下的程序中,k1,k2 表示的位置,在當前區域的哪一塊

a,b表示三個區域,當中左上的直接被忽略了。

kk1。kk2,表示移動到的了哪一個區域

if(!((a+p[i-1]+k1)%2==0&&(b+q[i-1]+k2)%2==1))表示移動到的區域的哪一塊。假設是左上直接忽略

 

 

[c-sharp]  view plain copy
  1. #ifndef MATRIXMOVE_H  
  2. #define MATRIXMOVE_H  
  3. #include <iostream>  
  4. #include <memory.h>  
  5. #define MAXN 101  
  6. class MatrixMove  
  7. {  
  8. public:  
  9.     MatrixMove()  
  10.     {  
  11.         std::cin>>n;  
  12.           
  13.         memset(f,0,sizeof(f));  
  14.         f[n+1][0][0]=1;  
  15.         f[n+1][0][1]=f[n+1][1][0]=f[n+1][1][1]=0;  
  16.         std::cin>>x>>y;  
  17.         for(int i=1;i<=n;i++)  
  18.         {  
  19.             p[n-i+1]=x%2;  
  20.             q[n-i+1]=y%2;  
  21.             x=x/2;  
  22.             y=y/2;  
  23.         }  
  24.         for(int i=n+1;i>=2;i--)  
  25.         {  
  26.             for(int k1=0;k1<=1;k1++)  
  27.                 for(int k2=0;k2<=1;k2++)  
  28.                     for(int a=0;a<=1;a++)  
  29.                         for(int b=0;b<=1;b++)  
  30.                         {  
  31.                             if(!(a==0&&b==1))  
  32.                             {  
  33.                                 int kk1=(a+p[i-1]+k1)/2;  
  34.                                 int kk2=(b+q[i-1]+k2)/2;  
  35.                                 if(!((a+p[i-1]+k1)%2==0&&(b+q[i-1]+k2)%2==1))  
  36.                                 {  
  37.                                     f[i-1][kk1][kk2]+=f[i][k1][k2];  
  38.                                 }  
  39.                             }  
  40.                         }  
  41.         }  
  42.         std::cout<<f[1][0][0]<<std::endl;  
  43.     }  
  44. private:  
  45.     int n;  
  46.     int x;  
  47.     int y;  
  48.     int f[MAXN][2][2];  
  49.     int p[MAXN];  
  50.     int q[MAXN];  
  51. };  
  52. #endif  
 

 

10, 中世紀劍士

n個人決斗,兩兩之間有強弱關系。強弱關系不傳遞,比如a>b,b>c,c>a。n個劍士圍成一個圈,一次抽簽。抽中的人和他右邊的人決斗,輸了的人出圈。

如今問是否存在一種決斗方式讓第k個人生出。計算可能勝出的的人數和方案。

 

這個題目讓我想起了圍成一個圈的猴子的題目,那個題目是約瑟夫問題。

 

和這個不一樣。

 

這個題目:一個人要勝出。則要勝了全部右邊的人,同一時候也要勝出左邊的人。由於是圍成一個圈,所以該人勝出的話,終於肯定是自己跟自己相遇。

那么,這樣的情況下,把圈展開成一個鏈,將該鏈延長一倍。假設i和i+n能夠相遇,則說明i能夠勝出。

i人向右決斗。i+n向左決斗

 

假設兩個人能夠相遇。用meet[i,j]來表示

meet[i,j]= true if meet[i,k] and meet[k,j] and (e[i,k] or e[j,k])=true

false

[cpp]  view plain copy
  1. #ifndef ACIENTSOLDIER_H  
  2. #define ACIENTSOLDIER_H  
  3. #include <iostream>  
  4. #include <memory.h>  
  5. class AcientSoldier  
  6. {  
  7. public:  
  8.     AcientSoldier()  
  9.     {  
  10.         std::cin>>n;  
  11.         a=new int*[n+1];  
  12.         meet=new bool*[2*n+1];  
  13.         for(int i=1;i<=n;i++)  
  14.         {  
  15.             a[i]=new int[n+1];  
  16.             meet[i]=new bool[2*n+1];  
  17.             meet[i+n]=new bool[2*n+1];  
  18.             memset(meet[i],0,sizeof(bool)*(2*n+1));  
  19.             memset(meet[i+n],0,sizeof(bool)*(2*n+1));  
  20.             meet[i][i+1]=true;  
  21.             meet[i+1][i]=true;  
  22.             meet[i+n][(i+n)%2+1]=true;  
  23.             meet[(i+n)%2+1][i+n]=true;  
  24.             for(int j=1;j<=n;j++)  
  25.                 std::cin>>a[i][j];  
  26.         }  
  27.         for(int k=1;k<=2*n;k++)  
  28.             for(int i=1;i<=2*n;i++)  
  29.                 for(int j=1;j<=2*n;j++)  
  30.                 {  
  31.                     if((i<k)&&(j>k)&&meet[i][k]&&meet[k][j]&&a[(i-1)%n+1][(k-1)%n+1]&&a[(j-1)%n+1][(k-1)%n+1])  
  32.                     {  
  33.                         meet[i][j]=true;  
  34.                         meet[j][i]=true;  
  35.                     }  
  36.                 }  
  37.         ans=0;    
  38.         for(int i=1;i<=n;i++)  
  39.         {  
  40.             if(meet[i][i+n])  
  41.                 ans++;  
  42.         }  
  43.         std::cout<<ans<<std::endl;;  
  44.           
  45.           
  46.     }  
  47. private:  
  48.     int n;  
  49.     int **a;  
  50.     bool **meet;  
  51.     int ans;  
  52. };  
  53. #endif  
 

11, 科學家實驗隕石

 

總共獲得了n1塊A星的和n2塊B星的,每次實驗須要各一塊,實驗完了后不能回收。

總共實驗min(n1,n2);

 

求每次試驗的絕對值和的最小值

 

先證明一下兩個遞增序列,a1<b1,a2<b2,  |a2-a1|+|b2-b1|<=|b2-a1|+|b1-a2|

 

------a1----------------b1-------------

         -  -

-

-  O  -

------a2----------------b2--------------

由上圖依據兩條邊之和大於第三邊能夠直接證明上面的式子

 

所以就能夠放心的使用動態規划了

對兩列數據由小到大排序,少的用i表示。多的用j表示

F[i,j]=min(F[i,j-1],F[i-1][j-1]+dis(i,j))      j>i

  F[i-1][j-1]+dis(i,j)      j=i

 

編程臨時省略,由於這個題目比較簡單

12,理想收入問題

僅僅有一元的本金,知道每天的股價為v[i], 求M天之后的理想收入,可以獲得的最大的收入

 

如果第i天的理想收入為F[i]

則有F[i]=max(F[j]/v[k])*v[i]; j<=k<i

令M[i]=max(F[j]/v[k])=max(M[i-1],MF[i-1]/v[i]);M[i]表示在第i天能夠得到的最多股票數,MF[i-1]表示前i-1天能夠取得的最大收入

MF[i]=Max(MF(i-1),F(i-1))

 

13. 一般的RMQ問題 (Range Minimum/Maximum Query)

 

對於給定的N與N個整數A[1...N],M與M對下標(x,y)(x<=y),對於每對下標(x,y)求出A[x...y]中最小數的下標
        解法:
                預處理,對於區間中每個點A,設計以A為左端點的floor(logN)+1個區間
                [A,A+2^0-1], [A,A+2^1-1],...,[A, A+2^floor(logN)-1]
在求解的過程中。依照區間長度依次求解。[A,A+2^(i+1)-1]。能夠通過兩個等長的子區間[A,A+2^i-1]和[A+2^i,A+2^(i+1)-1];
       取用方法:
A。當區間的重疊對結果無影響的時候,比如求最大最小值(本題所述,則依據區間[A,A+2^k-1]與[B-2^k+1,B]來計算
B,當區間的重疊對結果有影響的時候,比如統計數字的多少等等,將[A,B]划分為[A,A+2^k-1],
[A+2^K,A+2^k+2^(floor(log(B-(A+2^k)+1)))-1],每次使用對數直接獲得划分點。所以至多分成了floor(log(B-A+1))+1個區間。
所以這樣處理的時間復雜度為logN
本題中,所述的全部對數。均是以2為底。
  本題中。開一個數組,行下標表示區間開始端A。列坐標表示區間結束端B

 

[cpp]  view plain copy
  1. #include "stdafx.h"  
  2. #include <iostream>  
  3. #include <math.h>  
  4. using namespace std;  
  5. class RMQProblem  
  6. {  
  7.     int N;  
  8.     int M;  
  9.     int *data;  
  10.     int **b;  
  11. public:  
  12.     RMQProblem()  
  13.     {  
  14.         cin>>N>>M;  
  15.         data=new int[N+1];  
  16.         b=new int*[N+1];  
  17.         int tempRankNum=int(floor(log2(double(N))))+1;  
  18.         for(int i=1;i<=N;i++)  
  19.         {  
  20.             cin>>data[i];  
  21.             b[i]=new int[tempRankNum];  
  22.             b[i][0]=data[i];  
  23.         }  
  24.         for(int j=1;j<=tempRankNum;j++)  
  25.         {  
  26.             int k=1<<j;  
  27.             int kk=1<<(j-1);  
  28.             for(int i=1;i<=N-k+1;i++)  
  29.             {  
  30.                 b[i][j]=min(b[i][j-1],b[i+kk][j-1]);  
  31.             }  
  32.         }  
  33.           
  34.         int tempX,tempY,tempZ;  
  35.         for(int i=1;i<=M;i++)  
  36.         {  
  37.             cin>>tempX>>tempY;  
  38.             tempZ=int(floor(log2(tempY-tempX)));  
  39.             cout<<min(b[tempX][tempZ],b[tempY-int(1<<tempZ)][tempZ]);  
  40.               
  41.         }  
  42.     }  
  43. double log2(double x)  
  44. {  
  45.       
  46.     return log(double(x))/log(2.0);  
  47. }  
  48. int min(int x,int y)  
  49. {  
  50.     return x<y?x:y;  
  51. }  
  52. };  
14, 關於分石子問題的動態規划解法

有n個石頭。k個框子。把n個石頭按順序放入k個框子,比方1~3放入1,4~6放入2,要求最大重量的框子的重量最小的放法。

設石子的重量分別為Q1,Q2,...

g(i,j)=Qi+,...,+Qj;

f(i,j)表示把i個石子放到j個框的最大重量框的重量。

則f(i,j)=minj-1<=p<i (f(i,j),max(f(p,j-1),g(p+1,i)));

g(i,i)=Qi ,f(1,1)=g(1,1),f(i,1)=g(1,i);

1<=i<=n;

1<=j<=k;

假設提前把Si =Q1+,..,+Qi 計算出來,g(j,k)=Sk -Sj-1 則時間復雜度為O(N2 )


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM