1. 程式人生 > >NUC-ACM/ICPC 寒假訓練 簡單DP A - G題

NUC-ACM/ICPC 寒假訓練 簡單DP A - G題

解法 文件 oss 發現 最大 bsp class sample har

第一題:數塔 HDU - 2084

做法:

從第 i , j 個 節點往下走的最優解可以由從第 i+1,j 個節點往下走的最優解和第i+1,j+1個節點往下走的最優解得出,二者取其優即可。

代碼:

記憶化搜素

 1 #include<iostream>
 2 using namespace std;
 3 #include<cstdio>
 4 using namespace std;
 5 int n;
 6 int f[100][100];
 7 int v[100][100];
 8 int a[100][100];
 9 int fget(int x,int
y)//記憶化搜索求值 10 { 11 if (x<=0||y<=0) return 0;//如果超出範圍返回0 12 if (x>n||y>x) return 0;//如果超出範圍返回0 13 if (v[x][y]) return f[x][y];//如果之前訪問過那麽直接返回之前訪問過的值 14 v[x][y]=1;//標記訪問 15 int max=-10000000;//max為最大值 初始化一個很小的數 16 if (!v[x+1][y])//如果之前沒有遞歸過 x+1,y 的 值 17 { 18 f[x+1
][y]=fget(x+1,y);//遞歸獲得答案 19 } 20 if (!v[x+1][y+1])//如果之前沒有遞歸過 x+1,y+1 的 值 21 { 22 f[x+1][y+1]=fget(x+1,y+1);//遞歸獲得答案 23 } 24 25 //f[x][y]的值可以由 f[x+1][y] + a[x][y]得出 26 f[x][y]=f[x+1][y]+a[x][y]; 27 28 //當然, f[x][y]的值也可以由 f[x+1][y+1] + a[x][y]得出 29 //
那麽我們就取兩者中較大的那個 30 if (f[x][y]<=f[x+1][y+1]+a[x][y]) f[x][y]=f[x+1][y+1]+a[x][y]; 31 32 return f[x][y];//返回答案 33 } 34 void deal() 35 { 36 int i; 37 int j; 38 cin>>n; 39 //讀取 + 初始化 40 for (i=1;i<=n;i++) 41 for (j=1;j<=i;j++) 42 { 43 f[i][j]=0; 44 scanf("%d",&a[i][j]); 45 v[i][j]=0; 46 f[i][j]=a[i][j]; 47 } 48 int max=-1; 49 50 //fget(1,1)也就是數組頂部的元素也就是答案 51 cout<<fget(1,1)<<endl; 52 53 } 54 int main() 55 { 56 int n; 57 cin>>n;//n組數據 58 while (n--) 59 { 60 deal();//逐個處理 61 } 62 return 0; 63 }

遞推代碼:

 1 #include<iostream>
 2 #include<algorithm>
 3 #include<cstring>
 4 using namespace std;
 5 
 6 int main() {
 7     int a[105][105];
 8     int t, n, i, j;
 9     while(cin>>t) {
10         while(t--) {
11             cin>>n;
12             memset(a,0,sizeof(a));
13             for(i=0; i<n; i++)
14                 for(j=0; j<=i; j++)
15                     cin>>a[i][j];
16             for(i=n-1; i>=0; i--)
17                 for(j=0; j<=i; j++)
18                     a[i][j]=a[i][j]+max(a[i+1][j],a[i+1][j+1]);
19             cout<<a[0][0]<<endl;
20         }
21     }
22     return 0;
23 }

第二題:超級樓梯

來源:HDU - 2041

轉態轉移方程 f[i]=f[i-1]+f[i-2]

代碼:

記憶化搜索

 1 #include<iostream>
 2 using namespace std;
 3 #include<cstring>
 4 #include<cstdio>
 5 int dp[50];
 6 int dfs(int x){//遞歸得到x級樓梯的走法數量 
 7     if(x<0) return 0;//小於0級臺階當然是0 
 8     if(dp[x]>0)//已經有答案 
 9         return dp[x];
10     else
11         //該級臺階的答案可以由i-1階臺階的答案和i-2階的答案求和得到 
12         return dp[x]=dfs(x-1)+dfs(x-2);
13 }
14 int main(){
15     int t;
16     memset(dp,0,sizeof(dp));
17     dp[0]=1;
18     scanf("%d",&t);
19     while(t--){
20         int d;
21         scanf("%d",&d);
22         printf("%d\n",dfs(d-1));    
23     }
24     return 0;    
25 }

遞推:

 1 #include<iostream>
 2  using namespace std;
 3  int main(){
 4     int n;
 5     cin>>n;
 6         for(int j=0;j<n;j++){
 7             int m;
 8             cin>>m;
 9             int a[45];
10             for(int i=2;i<=m;i++){
11                 a[2]=1;
12                 a[3]=2;
13                 if(i>3)
14                 a[i]=a[i-1]+a[i-2];
15             }
16             cout<<a[m]<<endl;
17         }
18  }

第三題

母牛的故事

來源:HDU - 2018

第一年只有一開始的牛

第二年-第四年只有一開始的牛生了小牛

第五年第二年出生的小牛也生了小牛

第六年第三年出生的小牛也可以生母牛了

也就是說第i年時,3年前出生(包括第 前三年)的母牛 都有生育能力

那麽設dp[i]為第i年的牛的數量

那麽,顯然dp[i-1]為沒生小牛之前的牛的數量

dp[i-3]為3年前的牛的數量,也就是有生育能力的小牛的數量

那麽狀態轉移方程就可以得到:

dp[i]=dp[i-1]+dp[i-3]

代碼:

 1 #include<iostream>
 2 using namespace std;
 3 #include<cstdio>
 4 #include<cstring>
 5 int f[60];//第n年牛的數量
 6 int main(){  
 7     for(int i=1;i<=4;i++)
 8         f[i]=i;
 9     
10     //f[i-1]為去年牛的數量 f[i-3]為可以生牛的母牛的數量 也是新生的小牛的數量 
11     for(int i=5;i<=55;i++)
12         f[i]=f[i-1]+f[i-3];
13     int n;
14     while(scanf("%d",&n)!=EOF&&n){//循環讀入直到文件尾部或者n為0時結束 
15         printf("%d\n",f[n]);
16     }
17     return 0; 
18 }

第四題:

Bone Collector

來源:

HDU - 2602

題目大意:

已知N個糖果的重量和價值. 我們有一個口袋, 最多可以裝V重量的糖果. 問口袋最多能放多少價值的糖果進去? Input

輸入的第一行是T, 表示有T組數據. 每組數據由三行組成. 第一行包含兩個整數N和V(N <= 1000, V <= 1000). N表示糖果的個數, V表示口袋的載重. 第二行包含N個整數, 表示每一顆糖果的價值. 第三行包含N個整數, 表示每一顆糖果的重量.

Output

對每一組數據, 輸出口袋最終可以放進去糖果的價值.

Sample Input

1
5 10
1 2 3 4 5
5 4 3 2 1

Sample Output

14


做法:
一個簡單的01背包問題
解決0 1 背包問題的思想看背包九講
題解參見代碼註釋
 1 #include<iostream>
 2 using namespace std;
 3 #include<cstring>
 4 #include<cstdio>
 5 void deal();
 6 int main(){
 7     int t;
 8     scanf("%d",&t);
 9     while(t--)//t組數據 
10         deal();    
11     return 0;
12 }
13 int *value,*weight,*f;
14 void deal(){
15     int N,V;
16     scanf("%d%d",&N,&V);
17     value=new int[N];//每個糖果重量數組 
18     weight=new int[N];//每個糖果價值數組 
19     for(int i=0;i<N;i++)
20         scanf("%d",value+i);
21     for(int i=0;i<N;i++)
22         scanf("%d",weight+i);
23     f=new int[V+1];
24     memset(f,0,sizeof(int)*(V+1));//初始化 
25     for(int i=0;i<N;i++)//對於第i件物品 
26         for(int j=V;j>=0;j--)//當空間為j的時候 
27             if(j-weight[i]<0)//如果空間j放不下第i件物品 
28                 continue;//那就不管它好了
29             else
30             f[j]=max(f[j],f[j-weight[i]]+value[i]);//要麽把f[j]本身和放下第i件物品比一下看哪個大
31     int ans=0;
32     for(int i=0;i<=V;i++)
33         ans=max(ans,f[i]);//答案取最大的那個
34     printf("%d\n",ans); 
35     return;
36 }

第五題:

Common Subsequence

HDU - 1159

題解:

第一個字符串的前 i 個字符 和 第二個字符串 的 前 j 個字符 的最長公共子串 可以由

第一個字符串的前 i-1 個字符 和 第二個字符串的前j個字符 的最長公共子串

   第一個字符串的前 i 個字符 和 第二個字符串的前j-1個字符 的最長公共子串

   如果第一個字符串的第i個字符和第二個字符串的第j個字符相同

第一個字符串的前 i 個字符 和 第二個字符串 的 前 j 個字符 的最長公共子串 也可以由

     第一個字符串的前i-1個字符和第二個字符的前j-1個字符的最長公共子串的得到

  代碼:記憶化搜索

 1 #include<iostream>
 2 using namespace std;
 3 #include<cstdio>
 4 #include<cstring>
 5 const int maxn=1010;
 6 char *s1,*s2;
 7 int **dp;
 8 
 9 int dfs(int x,int y){//求s1的前x個字符與s2的前y個字符 
10     int ans=0;
11     if(x<0||y<0)//如果x或y中有一個超出字符串邊界 
12         return 0; //那麽返回0 
13     if(dp[x][y]>=0)//如果之前之前已經遞歸過此種狀態,直接返回 
14         return dp[x][y];
15     if(s1[x]==s2[y])//如果s1的第x個字符與s2的第y個字符相等 
16     ans=max(dfs(x-1,y-1)+1,ans);
17     ans=max(ans,dfs(x-1,y));//遞歸查找求s1的前x-1個字符與s2的前y個字符
18     ans=max(ans,dfs(x,y-1));//遞歸查找求s1的前x個字符與s2的前y-1個字符
19     return dp[x][y]=ans;//記錄此狀態的值並返回 
20 } 
21 
22 int main(){
23     s1=new char[maxn];//聲明內存 
24     s2=new char[maxn];
25     while(scanf("%s%s",s1,s2)!=EOF){
26         
27         int len1,len2;
28         len1=strlen(s1);//獲取字符串長度 
29         len2=strlen(s2);
30     
31         //聲明數組 
32         dp=new int*[len1+1];
33         for(int i=0;i<=len1;i++)
34             dp[i]=new int[len2+1];
35         
36         //初始化數組 
37         for(int i=0;i<=len1;i++)
38             for(int j=0;j<=len2;j++)
39                 dp[i][j]=-1;    
40         
41         printf("%d\n",dfs(len1-1,len2-1));
42         
43         //釋放內存 
44         for(int i=0;i<=len1;i++)
45             delete dp[i];
46         delete dp;
47         delete s1;
48         delete s2;
49         
50         s1=new char[maxn];//重新聲明 
51         s2=new char[maxn];
52     }
53     return 0;}

遞推:

#include <iostream>
#include<cstring>
using namespace std;
int dp[2001][2001];
int main() {
    char a[2001],b[2001];
    while(cin>>a>>b) {
        int i,j;
        int al,bl;
        al=strlen(a);
        bl=strlen(b);
        for(i=0; i<=al; i++)
            dp[i][0]=0;
        for(i=0; i<=bl; i++)
            dp[0][i]=0;
        for(i=1; i<=al; i++)
            for(j=1; j<=bl; j++) {
                if(a[i-1]==b[j-1])
                    dp[i][j]=dp[i-1][j-1]+1;
                else
                    dp[i][j] = dp[i-1][j] > dp[i][j-1] ? dp[i-1][j] : dp[i][j-1];
            }
        cout<<dp[al][bl]<<endl;
    }
    return 0;
}

第六題:

最少攔截系統

HDU - 1257

一種思路是

貪心去求解

把每一個導彈和它之前發射的所有子彈的攔截系統的高度去比較

如果有比它高的,那麽貪心地去選擇最低的那個可行的攔截系統

如果沒有比它高的攔截系統,新建一個攔截系統答案+1

第二種思路就是把問題轉化為最長遞增子序列的問題

和第K題解法相同

第七題:

Piggy-Bank

來源: HDU - 1114

題目大意:

在 ACM 能夠開展之前,必須準備預算,並獲得必要的財力支持。該活動的主要收入來自於 Irreversibly Bound Money (IBM)。思路很簡單。任何時候,某位 ACM 會員有少量的錢時,他將所有的硬幣投入到小豬儲錢罐中。這個過程不可逆,因為只有把小豬儲錢罐打碎才能取出硬幣。在足夠長的時間之後,小豬儲錢罐中有了足夠的現金,用於支付 ACM 活動所需的花費。

但是,小豬儲錢罐存在一個大的問題,即無法確定其中有多少錢。因此,我們可能在打碎小豬儲錢罐之後,發現裏面的錢不夠。顯然,我們希望避免這種不愉快的情況。唯一的可能是,稱一下小豬儲錢罐的重量,並嘗試猜測裏面的有多少硬幣。假定我們能夠精確判斷小豬儲錢罐的重量,並且我們也知道給定幣種的所有硬幣的重量。那麽,我們可以保證小豬儲錢罐中最少有多少錢。

你的任務是找出最差的情形,即判斷小豬儲錢罐中的硬幣最少有多少錢。我們需要你的幫助。不能再貿然打碎小豬儲錢罐了!

輸入

輸入包含 T 組測試數據。輸入文件的第一行,給出了 T 的值。

對於每組測試數據,第一行包含 E 和 F 兩個整數,它們表示空的小豬儲錢罐的重量,以及裝有硬幣的小豬儲錢罐的重量。兩個重量的計量單位都是 g (克)。小豬儲錢罐的重量不會超過 10 kg (千克),即 1 <= E <= F <= 10000 。每組測試數據的第二行,有一個整數 N (1 <= N <= 500),提供了給定幣種的不同硬幣有多少種。接下來的 N 行,每行指定一種硬幣類型,每行包含兩個整數 P 和 W (1 <= P <= 50000,1 <= W <=10000)。P 是硬幣的金額 (貨幣計量單位);W 是它的重量,以 g (克) 為計量單位。

輸出

對於每組測試數據,打印一行輸出。每行必須包含句子 “The minimum amount of money in the piggy-bank is X.” 其中,X 表示對於給定總重量的硬幣,所能得到的最少金額。如果無法恰好得到給定的重量,則打印一行 “This is impossible.” 。

解法:

一個完全背包問題,每個物品有無限種,問最優解

參見背包九講,完全背包問題

代碼:

 1 #include<iostream>
 2 using namespace std;
 3 #include<cstdio>
 4 int f[101000],a[10000],v[10000];
 5 int main()
 6 {
 7     int n;
 8     int t;
 9     cin>>t;
10     int l,r,fv;
11     while(t--)
12     {
13         scanf("%d%d",&l,&r);
14         fv=r-l;
15         cin>>n;
16         for (int i=1;i<=n;i++)
17             scanf("%d%d",&a[i],&v[i]);        
18         for (int i=1;i<=fv;i++)//f[i]儲存 總共i大小的重量 的 最小金額 
19             f[i]=0x7fffffff/2;    //初始值賦個很大的值 
20         f[0]=0;
21         for (int i=1;i<=n;i++)//對於第i種硬幣 
22         {
23             for (int j=v[i];j<=fv;j++)    //對於j大小的空間 
24             {
25                 f[j]=min(f[j],f[j-v[i]]+a[i]);    //f[j]的值為f[j]和f[j-v[i]]較小的那個值 
26             }
27         }                            
28         if (f[fv]==0x7fffffff/2) //如果依然是最大值,那麽就是不可能 
29             cout<<"This is impossible."<<endl;
30         else    //否則輸出答案 
31             printf("The minimum amount of money in the piggy-bank is %d.\n",f[fv]);
32     }
33     return 0;
34 } 

NUC-ACM/ICPC 寒假訓練 簡單DP A - G題