動態規划經典題


1、合並石子

https://www.cnblogs.com/Renyi-Fan/p/7392649.html(講得很好)方法其實有很多種的

思路:現將石子的前綴和計算出來,狀態為 f[i][j] 表示為合並 i 到 j 的最小值。

f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]+s[j]-s[i-1]);
【題目描述】
在一個操場上一排地擺放着N堆石子。現要將石子有次序地合並成一堆。規定每次只能選相鄰的2堆石子合並成新的一堆,並將新的一堆石子數記為該次合並的得分。

計算出將N堆石子合並成一堆的最小得分。

【輸入】
第一行為一個正整數N (2≤N≤100);

以下N行,每行一個正整數,小於10000,分別表示第i堆石子的個數(1≤i≤N)。

【輸出】
一個正整數,即最小得分。

【輸入樣例】
7
13
7
8
16
21
4
18
【輸出樣例】
239
合並石子
#include <bits/stdc++.h>
using namespace std;
const int N=130,inf=0x3f3f3f;; 
int s[N],c[N];
int f[N][N],n,x,t; 
inline int read()
{
    int x=0;char y='*',z=getchar();
    while(z<'0'||z>'9') y=z,z=getchar();
    while(z>='0'&&z<='9') x=(x<<3)+(x<<1)+(z-'0'),z=getchar();
    return y=='-'?-x:x;
}
int main()
{
    n=read();
    for(int i=1;i<=n;i++){
        x=read(); 
        s[i]=s[i-1]+x;
    } 
    memset(f,127/3,sizeof(f));
    for(int i=1;i<=n;i++) f[i][i]=0;
    for(int i=n-1;i>=1;i--)
        for(int j=i+1;j<=n;j++)
            for(int k=i;k<=j-1;k++)
                f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]+s[j]-s[i-1]);
    cout<<f[1][n]<<endl;    
    return 0;
}
View Code

合並石子,加強(圓形)求最大值和最小值

思路,形成了一個環之后,用兩倍的大小來模擬環形

Description
  在一個圓形操場的四周擺放N堆石子,現要將石子有次序地合並成一堆.規定每次只能選相鄰的2堆合並成新的一堆,並將新的一堆的石子數,記為該次合並的得分。試設計出1個算法,計算出將N堆石子合並成1堆的最小得分和最大得分。
Input
  輸入第一行為n(n<=100),表示有n堆石子
  第二行為n個用空格隔開的整數,依次表示這n堆石子的石子數量(<=1000)
Output
 輸出將n堆石子合並成一堆的最小得分和將n堆石子合並成一堆的最大得分
Sample Input
3
1 2 3
Sample Output
9
11
合並石子(圓形)
#include <bits/stdc++.h>
using namespace std;
const int N=210,inf=0x3f3f3f;; 
int s[N],a[N];
int f[N][N],g[N][N],n,x,minn,maxn; 
inline int read()
{
    int x=0;char y='*',z=getchar();
    while(z<'0'||z>'9') y=z,z=getchar();
    while(z>='0'&&z<='9') x=(x<<3)+(x<<1)+(z-'0'),z=getchar();
    return y=='-'?-x:x;
}
int main()
{
    n=read();
    for(int i=1;i<=n;i++){
        a[i]=read(); 
        a[i+n]=a[i];//
    } 
    for(int i=2;i<=2*n;i++) a[i]+=a[i-1];
    for(int i=2*n-1;i>=1;i--)
        for(int j=i+1;j<=i+n-1;j++)
        {
            f[i][j]=inf;
            for(int k=i;k<=j-1;k++)
            {
                f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]+a[j]-a[i-1]);
                g[i][j]=max(g[i][j],g[i][k]+g[k+1][j]+a[j]-a[i-1]);
            }    
        }
                
    minn=inf;maxn;
    for(int i=1;i<=n;i++){
        maxn=max(maxn,g[i][i+n-1]);
        minn=min(minn,f[i][i+n-1]);
    }
    cout<<minn<<endl<<maxn<<endl;
    return 0;
}
View Code

還有一種思路:%n

2、乘積最大

思路:主要是輸入的問題,將每個位置的數保存下來,用a[i][i]表示,用數組a[i][j]表示位置i到j的值是多少;

【題目描述】
今年是國際數學聯盟確定的“2000——世界數學年”,又恰逢我國著名數學家華羅庚先生誕辰90周年。在華羅庚先生的家鄉江蘇金壇,組織了一場別開生面的數學智力競賽的活動,你的一個好朋友XZ也有幸得以參加。活動中,主持人給所有參加活動的選手出了這樣一道題目:

設有一個長度為N的數字串,要求選手使用K個乘號將它分成K+1個部分,找出一種分法,使得這K+1個部分的乘積最大。

同時,為了幫助選手能夠正確理解題意,主持人還舉了如下的一個例子:

有一個數字串:312, 當N=3,K=1時會有以下兩種分法:

13*12=36

231*2=62

這時,符合題目要求的結果是:31*2=62。

現在,請你幫助你的好朋友XZ設計一個程序,求得正確的答案。

【輸入】
第一行共有2個自然數N,K(6≤N≤101≤K≤6)

第二行是一個長度為N的數字串。

【輸出】
輸出所求得的最大乘積(一個自然數)。

【輸入樣例】
4 2
1231
【輸出樣例】
62
乘積最大
#include <bits/stdc++.h>
using namespace std;
const int N=210,inf=0x3f3f3f;; 
long long a[N][N],f[N][N],s;
int g[N][N],n,m,minn,maxn; 
inline int read()
{
    int x=0;char y='*',z=getchar();
    while(z<'0'||z>'9') y=z,z=getchar();
    while(z>='0'&&z<='9') x=(x<<3)+(x<<1)+(z-'0'),z=getchar();
    return y=='-'?-x:x;
}
int main()
{
    n=read();m=read();
    scanf("%lld",&s);
    for(int i=n;i>=1;i--){
        a[i][i]=s%10;//i到i位置的數 
        s/=10;
    }
    for(int i=2;i<=n;i++) 
    {
        for(int j=i-1;j>=1;j--)//計算j到i的數字大小
            a[j][i]=a[j][i-1]*10+a[i][i];
    }
    for(int i=1;i<=n;i++)
    {
        f[i][0]=a[1][i];//沒有乘號的預處理 
    }
    for(int k=1;k<=m;k++)
        for(int i=k+1;i<=n;i++)//有k個乘號,則至少有k+1個數字
            for(int j=k;j<i;j++)//j就是前面的 
                f[i][k]=max(f[i][k],f[j][k-1]*a[j+1][i]); 
    cout<<f[n][m]<<endl; 
    return 0;
}
View Code

3、編輯距離

這個題就類似公共子序列,遇到不相同的刪除或者插入,相當於一個f[i-1][j]和f[i][j-1],如果是更改的話就是f[i-1][j-1];

if  (s1[i-1]==s2[j-1]) f[i][j]=f[i-1][j-1];

else f[i][j]=min(min(f[i-1][j],f[i][j-1]),f[i-1][j-1])+1;

Description
  設A和B是兩個字符串。我們要用最少的字符操作次數,將字符串A轉換為字符串B。這里所說的字符操作共有三種:
    1、刪除一個字符;
    2、插入一個字符;
    3、將一個字符改為另一個字符。
  對任意的兩個字符串A和B,計算出將字符串A變換為字符串B所用的最少字符操作次數。
Input
  第一行為字符串A;第二行為字符串B;字符串A和B的長度均小於2000。
Output
  只有一個正整數,為最少字符操作次數。
Sample Input
sfdqxbw
gfdgw
Sample Output
4
編輯距離
#include <bits/stdc++.h>
using namespace std;
const int N=2100,inf=0x3f3f3f;; 
int f[N][N],n,m,minn,maxn; 
char s1[N],s2[N];
inline int read()
{
    int x=0;char y='*',z=getchar();
    while(z<'0'||z>'9') y=z,z=getchar();
    while(z>='0'&&z<='9') x=(x<<3)+(x<<1)+(z-'0'),z=getchar();
    return y=='-'?-x:x;
}
int main()
{
    scanf("%s %s",&s1,&s2);
    int n=strlen(s1),m=strlen(s2);
    for(int i=1;i<=n;i++) f[i][0]=i;
    for(int i=1;i<=m;i++) f[0][i]=i;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
        {
            if(s1[i-1]==s2[j-1]) f[i][j]=f[i-1][j-1];
            else{
                f[i][j]=min(min(f[i-1][j],f[i][j-1]),f[i-1][j-1])+1;
            }
        }
    cout<<f[n][m]<<endl;
    return 0;
}
View Code

4、方格取數:(有一個總結了,就不在這里寫了)

5、復制書稿

同樣也是需要求出前綴和,然后在來進行計算,輸出需要注意一下

Description
  現在要把m本有順序的書分給k個人復制(抄寫),每一個人的抄寫速度都一樣,一本書不允許給兩個(或以上)的人抄寫,分給每一個人的書,必須是連續的,比如不能把第一、第三和第四本書給同一個人抄寫。
  現在請你設計一種方案,使得復制時間最短。復制時間為抄寫頁數最多的人用去的時間。
Input
  第一行兩個整數m,k;(k≤m≤500)
  第二行m個整數,第i個整數表示第i本書的頁數。
Output
  共k行,每行兩個整數,第i行表示第i個人抄寫的書的起始編號和終止編號。k行的起始編號應該從小到大排列,如果有多解,則盡可能讓前面的人少抄寫。
Sample Input
9 3            
1 2 3 4 5 6 7 8 9
Sample Output
1 5
6 7
8 9
復制書稿
#include <bits/stdc++.h>
using namespace std;
const int N=505,inf=0x3f3f3f;; 
int a[N],s[N],f[N][N],n,m,minn,maxn,x,y,z; 
inline int read()
{
    int x=0;char y='*',z=getchar();
    while(z<'0'||z>'9') y=z,z=getchar();
    while(z>='0'&&z<='9') x=(x<<3)+(x<<1)+(z-'0'),z=getchar();
    return y=='-'?-x:x;
}
void print(int x,int ans){
    if(!x) return;
    for(int i=x;i>=0;i--){
        if(s[x]-s[i-1]>ans||!i){
            print(i,ans);
            printf("%d %d\n",i+1,x);
            break;
        }
    }
}
int main()
{
    n=read();m=read();
    memset(f,127/3,sizeof(f));
    for(int i=1;i<=n;i++) {
        a[i]=read();
        s[i]=s[i-1]+a[i];
        f[1][i]=s[i];
    }
    for(int i=2;i<=m;i++)//人數
        for(int j=1;j<=n;j++)//
            for(int k=2;k<=j;k++)//枚舉最后一個人復印開始的位置
            {
                f[i][j]=min(f[i][j],max(f[i-1][k-1],s[j]-s[k-1]));
             } 
    print(n,f[m][n]);
    return 0;
}
View Code

6、櫥窗布置

這里初始化和輸出需要注意下,狀態表示f[i][j]:i束花放在前j個花瓶中

【題目描述】
假設以最美觀的方式布置花店的櫥窗,有FF束花,每束花的品種都不一樣,同時,至少有同樣數量的花瓶,被按順序擺成一行,花瓶的位置是固定的,並從左到右,從11到VV順序編號,VV是花瓶的數目,編號為11的花瓶在最左邊,編號為VV的花瓶在最右邊,花束可以移動,並且每束花用11到FF的整數惟一標識,標識花束的整數決定了花束在花瓶中列的順序即如果i<ji<j,則花束ii必須放在花束jj左邊的花瓶中。

例如,假設杜鵑花的標識數為11,秋海棠的標識數為22,康乃馨的標識數為33,所有的花束在放人花瓶時必須保持其標識數的順序,即:杜鵑花必須放在秋海棠左邊的花瓶中,秋海棠必須放在康乃馨左邊的花瓶中。如果花瓶的數目大於花束的數目,則多余的花瓶必須空,即每個花瓶中只能放一束花。

每一個花瓶的形狀和顏色也不相同,因此,當各個花瓶中放人不同的花束時會產生不同的美學效果,並以美學值(一個整數)來表示,空置花瓶的美學值為00。在上述例子中,花瓶與花束的不同搭配所具有的美學值,可以用如下表格表示。

根據表格,杜鵑花放在花瓶22中,會顯得非常好看,但若放在花瓶44中則顯得很難看。

為取得最佳美學效果,必須在保持花束順序的前提下,使花的擺放取得最大的美學值,如果具有最大美學值的擺放方式不止一種,則輸出任何一種方案即可。題中數據滿足下面條件:1≤F≤1001≤F≤100,F≤V≤100F≤V≤100,−50≤Aij≤5050≤Aij≤50,其中AijAij是花束ii擺放在花瓶jj中的美學值。輸入整數FF,VV和矩陣(Aij)(Aij),輸出最大美學值和每束花擺放在各個花瓶中的花瓶編號。


花瓶1    花瓶2    花瓶3    花瓶4    花瓶5
杜鵑花    7    23    -5    -24    16
秋海棠    5    21    -4    10    23
康乃馨    -21    5    -4    -20    20
假設條件:

1≤F≤1001≤F≤100,其中 FF 為花束的數量,花束編號從 11 至 FF 。

F≤V≤100F≤V≤100,其中 VV 是花瓶的數量。

−50≤Aij≤5050≤Aij≤50,其中 AijAij 是花束 ii 在花瓶 jj 中的美學值。

【輸入】
第一行包含兩個數:F,VF,V。

隨后的FF行中,每行包含VV個整數,AijAij 即為輸入文件中第(i+1i+1)行中的第jj個數。

【輸出】
第一行是程序所產生擺放方式的美學值。

第二行必須用FF個數表示擺放方式,即該行的第KK個數表示花束K所在的花瓶的編號。

【輸入樣例】
3 5 
7 23524 16
5 21 -4 10 23
-21 5 -4 -20 20
【輸出樣例】
53 
2 4 5
櫥窗布置(flower)
#include <bits/stdc++.h>
using namespace std;
const int N=101,inf=0x3f3f3f3f;; 
int a[N][N],s[N],f[N][N],n,m,minn,maxn,x,y,z; 
inline int read()
{
    int x=0;char y='*',z=getchar();
    while(z<'0'||z>'9') y=z,z=getchar();
    while(z>='0'&&z<='9') x=(x<<3)+(x<<1)+(z-'0'),z=getchar();
    return y=='-'?-x:x;
}
void print(int x,int ans){
    int i;
    if(x>0){
        i=x;
        while(f[x][i]!=ans){
            i++;
        }
        print(x-1,ans-a[x][i]);
        cout<<i<<" ";
    }
}
int main()
{
    n=read();m=read();
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            a[i][j]=read();
    memset(f,128,sizeof(f));
    for(int i=0;i<N;i++)
        f[0][i]=0;
    for(int i=1;i<=n;i++)
        for(int j=i;j<=m-n+i;j++)//保證最后剩下的瓶子夠剩下的花束用 
            for(int k=i;k<=j;k++)
            {
                f[i][j]=max(f[i][j],f[i-1][k-1]+a[i][k]);//i束花放在前j個花瓶中 
            }
    int c=-inf;
    for(int i=n;i<=m;i++)
        c=max(c,f[n][i]);
    cout<<c<<endl;
    print(n,c);
    return 0;
} 
View Code

7、滑雪

其實就相當於dfs的記憶化搜索

【題目描述】
小明喜歡滑雪,因為滑雪的確很刺激,可是為了獲得速度,滑的區域必須向下傾斜,當小明滑到坡底,不得不再次走上坡或等着直升機來載他,小明想知道在一個區域中最長的滑坡。滑坡的長度由滑過點的個數來計算,區域由一個二維數組給出,數組的每個數字代表點的高度。下面是一個例子:

1161514132172423123182522114192021105678912345161718196152425207142322218131211109
一個人可以從某個點滑向上下左右相鄰四個點之一,當且僅當高度減小,在上面的例子中,一條可行的滑坡為25-24-17-16-1(從25開始到1結束),當然25-24……2-1更長,事實上這是最長的一條。

【輸入】
輸入的第一行為表示區域的二維數組的行數R和列數C(1≤R、C≤100),下面是R行,每行有C個數代表高度。

【輸出】
輸出區域中最長的滑坡長度。

【輸入樣例】
5 5
1 2 3 4 5
16 17 18 19 6
15 24 25 20 7
14 23 22 21 8
13 12 11 10 9
【輸出樣例】
25
滑雪
#include <bits/stdc++.h>
using namespace std;
const int N=101,inf=0x3f3f3f3f;; 
int a[N][N],s[N],f[N][N],n,m,t; 
int d[4][4]={{-1,0},{0,1},{1,0},{0,-1}};
inline int read()
{
    int x=0;char y='*',z=getchar();
    while(z<'0'||z>'9') y=z,z=getchar();
    while(z>='0'&&z<='9') x=(x<<3)+(x<<1)+(z-'0'),z=getchar();
    return y=='-'?-x:x;
}
int dfs(int x,int y)
{
    if(f[x][y]) return f[x][y];
    int num=1;
    for(int i=0;i<4;i++)
    {
        int nx=x+d[i][0],ny=y+d[i][1];
        if(nx>=1&&nx<=n&&ny>=1&&ny<=m&&a[x][y]<a[nx][ny])
        {
            int temp=dfs(nx,ny)+1;
            num=max(num,temp);
        }
    }
    f[x][y]=num;
    return num;
}
int main()
{
    n=read();m=read();
    int ans=0;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
        {
            a[i][j]=read();
        }
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
        {
            t=dfs(i,j);
            f[i][j]=t;
            ans=max(ans,t);
        }
    cout<<ans<<endl;
    return 0;
} 
View Code

8、公共子序列(就是最大上升子序列的多組數據版)

【題目描述】
我們稱序列Z=<z1,z2,...,zk>Z=<z1,z2,...,zk>是序列X=<x1,x2,...,xm>X=<x1,x2,...,xm>的子序列當且僅當存在嚴格上升的序列<i1,i2,...,ik><i1,i2,...,ik>,使得對j=1,2,...,k,有xij=zjxij=zj。比如Z=<a,b,f,c> 是X=<a,b,c,f,b,c>的子序列。

現在給出兩個序列X和Y,你的任務是找到X和Y的最大公共子序列,也就是說要找到一個最長的序列Z,使得Z既是X的子序列也是Y的子序列。

【輸入】
輸入包括多組測試數據。每組數據包括一行,給出兩個長度不超過200的字符串,表示兩個序列。兩個字符串之間由若干個空格隔開。

【輸出】
對每組輸入數據,輸出一行,給出兩個序列的最大公共子序列的長度。

【輸入樣例】
abcfbc abfcab
programming contest 
abcd mnp
【輸出樣例】
4
2
0
公共子序列
#include <bits/stdc++.h>
using namespace std;
const int N=501,inf=0x3f3f3f3f;; 
int f[N][N],n,m,t; 
char a[N],b[N];
inline int read()
{
    int x=0;char y='*',z=getchar();
    while(z<'0'||z>'9') y=z,z=getchar();
    while(z>='0'&&z<='9') x=(x<<3)+(x<<1)+(z-'0'),z=getchar();
    return y=='-'?-x:x;
}
int main()
{
    while(scanf("%s %s",a+1,b+1)!=EOF)
    {
        int len1=strlen(a+1),len2=strlen(b+1);
        for(int i=1;i<=len1;i++) f[i][0]=0;
        for(int j=1;j<=len2;j++) f[0][j]=0;
        for(int i=1;i<=len1;i++)
            for(int j=1;j<=len2;j++)
            {
                if(a[i]==b[j]) f[i][j]=f[i-1][j-1]+1;
                else f[i][j]=max(f[i-1][j],f[i][j-1]);
            }
        cout<<f[len1][len2]<<endl;
    }
    return 0;
} 
View Code

9、糖果

比較巧妙的思想就是將狀態表示為除以k的余數;f[i][j]表示i件產品的糖果個數除以k的余數的最大總數

Description
  由於在維護世界和平的事務中做出巨大貢獻,Dzx被贈予糖果公司2010年5月23日當天無限量糖果免費優惠券。在這一天,Dzx可以從糖果公司的N件產品中任意選擇若干件帶回家享用。糖果公司的N件產品每件都包含數量不同的糖果。Dzx希望他選擇的產品包含的糖果總數是K的整數倍,這樣他才能平均地將糖果分給幫助他維護世界和平的伙伴們。當然,在滿足這一條件的基礎上,糖果總數越多越好。Dzx最多能帶走多少糖果呢?
  注意:Dzx只能將糖果公司的產品整件帶走。
Input
  第一行包含兩個整數N( 1<=N<=100 )和K( 1<=K<=100 )。
  以下N行每行1個整數,表示糖果公司該件產品中包含的糖果數目,不超過1000000。
Output
  符合要求的最多能達到的糖果總數,如果不能達到K的倍數這一要求,輸出0。
Sample Input
5 7
1
2
3
4
5
Sample Output
14
糖果
#include <bits/stdc++.h>
using namespace std;
const int N=1005,inf=0x3f3f3f3f;; 
int f[N][N],n,m; 
int a[N];
inline int read()
{
    int x=0;char y='*',z=getchar();
    while(z<'0'||z>'9') y=z,z=getchar();
    while(z>='0'&&z<='9') x=(x<<3)+(x<<1)+(z-'0'),z=getchar();
    return y=='-'?-x:x;
}
int main()
{
    n=read();m=read();
    for(int i=1;i<=n;i++) a[i]=read();
    for(int i=1;i<m;i++) f[0][i]=-inf;
    for(int i=1;i<=n;i++)
        for(int j=0;j<m;j++)
            f[i][j]=max(f[i-1][j],f[i-1][(m+j-a[i]%m)%m]+a[i]);//
    cout<<f[n][0]<<endl;
    return 0;
} 
View Code

10、雞蛋的硬度

對於這個題:當時寫的是我感覺自己不是很理解這個最優策略為啥這樣做

最優策略指在最壞情況下所需要的扔雞蛋次數最少的策略。

代碼就先放在這里

Description
  最近XX公司舉辦了一個奇怪的比賽:雞蛋硬度之王爭霸賽。參賽者是來自世界各地的母雞,比賽的內容是看誰下的蛋最硬,更奇怪的是XX公司並不使用什么精密儀器來測量蛋的硬度,他們采用了一種最老土的辦法--從高度扔雞蛋--來測試雞蛋的硬度,如果一次母雞下的蛋從高樓的第a層摔下來沒摔破,但是從a+1層摔下來時摔破了,那么就說這只母雞的雞蛋的硬度是a。你當然可以找出各種理由說明這種方法不科學,比如同一只母雞下的蛋硬度可能不一樣等等,但是這不影響XX公司的爭霸賽,因為他們只是為了吸引大家的眼球,一個個雞蛋從100 層的高樓上掉下來的時候,這情景還是能吸引很多人駐足觀看的,當然,XX公司也絕不會忘記在高樓上掛一條幅,寫上“XX公司”的字樣--這比賽不過是XX 公司的一個另類廣告而已。
  勤於思考的小A總是能從一件事情中發現一個數學問題,這件事也不例外。“假如有很多同樣硬度的雞蛋,那么我可以用二分的辦法用最少的次數測出雞蛋的硬度”,小A對自己的這個結論感到很滿意,不過很快麻煩來了,“但是,假如我的雞蛋不夠用呢,比如我只有1個雞蛋,那么我就不得不從第1層樓開始一層一層的扔,最壞情況下我要扔100次。如果有2個雞蛋,那么就從2層樓開始的地方扔……等等,不對,好像應該從1/3的地方開始扔才對,嗯,好像也不一定啊……3個雞蛋怎么辦,4個,5個,更多呢……”,和往常一樣,小A又陷入了一個思維僵局,與其說他是勤於思考,不如說他是喜歡自找麻煩。
  好吧,既然麻煩來了,就得有人去解決,小A的麻煩就靠你來解決了:)
Input
  輸入包括多組數據,每組數據一行,包含兩個正整數n和m( 1<=n<=100,1<=m<=10 ),其中n表示樓的高度,m表示你現在擁有的雞蛋個數,這些雞蛋硬度相同(即它們從同樣高的地方掉下來要么都摔碎要么都不碎),並且小於等於n。你可以假定硬度為x的雞蛋從高度小於等於x的地方摔無論如何都不會碎(沒摔碎的雞蛋可以繼續使用),而只要從比x高的地方扔必然會碎。
  對每組輸入數據,你可以假定雞蛋的硬度在0至n之間,即在n+1層扔雞蛋一定會碎。
Output
  對於每一組輸入,輸出一個整數,表示使用最優策略在最壞情況下所需要的扔雞蛋次數。
Sample Input
100 1
100 2
Sample Output
100
14
雞蛋的硬度
#include <bits/stdc++.h>
using namespace std;
const int N=1005,inf=0x3f3f3f3f;; 
int f[N][N];//第i樓和扔j次蛋 
int n,m; 
int a[N];
inline int read()
{
    int x=0;char y='*',z=getchar();
    while(z<'0'||z>'9') y=z,z=getchar();
    while(z>='0'&&z<='9') x=(x<<3)+(x<<1)+(z-'0'),z=getchar();
    return y=='-'?-x:x;
}
int main()
{
    while(scanf("%d %d",&n,&m)!=EOF){
        for(int i=1;i<=n;i++)
            for(int j=0;j<=m;j++) 
                f[i][j]=i;
        for(int i=1;i<=n;i++)
            for(int k=1;k<=i;k++)//中間變量,在這個地方進行判斷轉折與否(也就是碎了往下走,沒碎往上走)
                for(int j=2;j<=m;j++)
                    f[i][j]=min(f[i][j],max(f[k-1][j-1],f[i-k][j])+1);
        cout<<f[n][m]<<endl;
    }
    
     
    return 0;
} 
View Code

11、大盜阿福

思路不難,就是要么不取,就等於f[i-1],要么偷這一個上一個就不能偷,就是f[i-2]+a[i];

f[i]=max(f[i-1],f[i-2]+a[i]);
【題目描述】
阿福是一名經驗豐富的大盜。趁着月黑風高,阿福打算今晚洗劫一條街上的店鋪。

這條街上一共有 NN 家店鋪,每家店中都有一些現金。阿福事先調查得知,只有當他同時洗劫了兩家相鄰的店鋪時,街上的報警系統才會啟動,然后警察就會蜂擁而至。

作為一向謹慎作案的大盜,阿福不願意冒着被警察追捕的風險行竊。他想知道,在不驚動警察的情況下,他今晚最多可以得到多少現金?

【輸入】
輸入的第一行是一個整數T(T≤50)T(T≤50) ,表示一共有T組數據。

接下來的每組數據,第一行是一個整數N(1≤N≤100,000)N(1≤N≤100,000) ,表示一共有NN家店鋪。第二行是NN個被空格分開的正整數,表示每一家店鋪中的現金數量。每家店鋪中的現金數量均不超過10001000。

【輸出】
對於每組數據,輸出一行。該行包含一個整數,表示阿福在不驚動警察的情況下可以得到的現金數量。

【輸入樣例】
2
3
1 8 2
4
10 7 6 14
【輸出樣例】
8
24
【提示】
對於第一組樣例,阿福選擇第22家店鋪行竊,獲得的現金數量為88。

對於第二組樣例,阿福選擇第11和44家店鋪行竊,獲得的現金數量為10+14=2410+14=24
大盜阿福
#include <bits/stdc++.h>
using namespace std;
const int N=100005,inf=0x3f3f3f3f;; 
int f[N];//第i樓和扔j次蛋 
int n,m; 
int a[N];
inline int read()
{
    int x=0;char y='*',z=getchar();
    while(z<'0'||z>'9') y=z,z=getchar();
    while(z>='0'&&z<='9') x=(x<<3)+(x<<1)+(z-'0'),z=getchar();
    return y=='-'?-x:x;
}
int main()
{
    n=read();
    while(n--){
        m=read();
        for(int i=1;i<=m;i++)    a[i]=read();
        f[0]=0;f[1]=a[1];
        for(int i=2;i<=m;i++) 
            f[i]=max(f[i-1],f[i-2]+a[i]);
        cout<<f[m]<<endl;
    }
    return 0;
} 
View Code

12、股票買賣

因為是買賣兩次,兩邊循環兩次

【題目描述】
最近越來越多的人都投身股市,阿福也有點心動了。謹記着“股市有風險,入市需謹慎”,阿福決定先來研究一下簡化版的股票買賣問題。

假設阿福已經准確預測出了某只股票在未來N天的價格,他希望買賣兩次,使得獲得的利潤最高。為了計算簡單起見,利潤的計算方式為賣出的價格減去買入的價格。

同一天可以進行多次買賣。但是在第一次買入之后,必須要先賣出,然后才可以第二次買入。

現在,阿福想知道他最多可以獲得多少利潤。

【輸入】
輸入的第一行是一個整數T(T≤50),表示一共有T組數據。

接下來的每組數據,第一行是一個整數N(1≤N≤100,000),表示一共有N天。第二行是 N 個被空格分開的整數,表示每天該股票的價格。該股票每天的價格的絕對值均不會超過1,000,000。

【輸出】
對於每組數據,輸出一行。該行包含一個整數,表示阿福能夠獲得的最大的利潤。

【輸入樣例】
3
7
5 14 -2 4 9 3 17
6
6 8 7 4 1 -2
4
18 9 5 2
【輸出樣例】
28
2
0
【提示】
對於第一組樣例,阿福可以第1次在第1天買入(價格為5),然后在第2天賣出(價格為14)。第2次在第3天買入(價格為-2),然后在第7天賣出(價格為17)。一共獲得的利潤是(14-5)+(17-(-2))=28。

對於第二組樣例,阿福可以第1次在第1天買入(價格為6),然后在第2天賣出(價格為8)。第2次仍然在第2天買入,然后在第2天賣出。一共獲得的利潤是8-6=2。

對於第三組樣例,由於價格一直在下跌,阿福可以隨便選擇一天買入之后迅速賣出。獲得的最大利潤為0。

經典算法Baidu搜索,深刻體會。
股票買賣
#include <bits/stdc++.h>
using namespace std;
const int N=100005,inf=0x3f3f3f3f;; 
int f[N],g[N];//f為i天前的最大利潤 
int n,m,minn,maxx; 
int a[N];
inline int read()
{
    int x=0;char y='*',z=getchar();
    while(z<'0'||z>'9') y=z,z=getchar();
    while(z>='0'&&z<='9') x=(x<<3)+(x<<1)+(z-'0'),z=getchar();
    return y=='-'?-x:x;
}
int main()
{
    n=read();
    while(n--){
        m=read();
        memset(a,0,sizeof(a));
        memset(f,0,sizeof(f));
        memset(g,0,sizeof(g));
        for(int i=1;i<=m;i++)    a[i]=read();
        f[1]=0;g[1+n]=0; 
        minn=inf;maxx=-inf; 
        for(int i=1;i<=m;i++){
            minn=min(minn,a[i]);
            f[i]=max(f[i-1],a[i]-minn);//i天前的最大 
        }
        for(int i=m;i>=1;i--)
        {
            maxx=max(maxx,a[i]);
            g[i]=max(g[n-1],maxx-a[i]);
        }
        int ans=-inf;
        for(int i=1;i<=m;i++)
            ans=max(ans,f[i]+g[i]);
        cout<<ans<<endl;
    }
    return 0;
} 
View Code

13、鳴人的影分身

我自己的做法其實就是分蘋果那種做法,用動態規划的話,參考了一下別人的代碼

【題目描述】
在火影忍者的世界里,令敵人捉摸不透是非常關鍵的。我們的主角漩渦鳴人所擁有的一個招數——多重影分身之術——就是一個很好的例子。

影分身是由鳴人身體的查克拉能量制造的,使用的查克拉越多,制造出的影分身越強。

針對不同的作戰情況,鳴人可以選擇制造出各種強度的影分身,有的用來佯攻,有的用來發起致命一擊。

那么問題來了,假設鳴人的查克拉能量為M,他影分身的個數最多為N,那么制造影分身時有多少種(用K表示)不同的分配方法?(影分身可以被分配到0點查克拉能量)

【輸入】
第一行是測試數據的數目t(0≤t≤20)。以下每行均包含二個整數M和N(1≤M,N≤10),以空格分開。

【輸出】
對輸入的每組數據M和N,用一行輸出相應的K。

【輸入樣例】
1
7 3
【輸出樣例】
8
鳴人的影分身
int t,n,m;
int f[11][11];
int main(){
    cin>>t;
    for(int i=0;i<=10;i++){//能量
        for(int j=0;j<=10;j++){//身體數
            if(j==1||i==0||i==1) f[i][j]=1;
            //一個身體、0點或1點能量-----都只有一種方案
            else if(j>i) f[i][j]=f[i][i];
            else f[i][j]=f[i-j][j]+f[i][j-1];  //第一種情況:每個分身都有1點能量加上多余的能產生的
                //第二種情況:減少一個身體能夠產生的
            //分為無0和有0 !!!!!記住
        }
    }
    while(t--){
        cin>>n>>m;
        cout<<f[n][m]<<endl;
    }
return 0;
}
動態規划做法
#include <bits/stdc++.h>
using namespace std;
const int N=100005,inf=0x3f3f3f3f;; 
int g[N];//f為i天前的最大利潤 
int t; 
int a[N];
inline int read()
{
    int x=0;char y='*',z=getchar();
    while(z<'0'||z>'9') y=z,z=getchar();
    while(z>='0'&&z<='9') x=(x<<3)+(x<<1)+(z-'0'),z=getchar();
    return y=='-'?-x:x;
}
int f(int m,int n)
{
    if(m==0||n==1) return 1;
    if(m<n) return f(m,m);
    else return f(m,n-1)+f(m-n,n);
 }
int main()
{
    int n,m;
    t=read();
    while(t--){
        m=read();n=read();
        cout<<f(m,n)<<endl;
    }
    return 0;
} 
分蘋果做法

14、數的划分

Description
  將整數n分成k份,且每份不能為空,任意兩份不能相同(不考慮順序)。
  例如:n=7,k=3,下面三種分法被認為是相同的。
  115151511;
  問有多少種不同的分法。 輸出一個整數,即不同的分法。
Input
  兩個整數n,k( 6 < n <= 2002 <= k <= 6),中間用單個空格隔開。
Output
  一個整數,即不同的分法。
Sample Input

7 3
Sample Output
4
Hint
  四種分法為:115124133223
數的划分
//yrnddup c++ code
#include <bits/stdc++.h>
using namespace std;
const int N=1005; 
int f[N][N];
int t; 
int a[N];
inline int read()
{
    int x=0;char y='*',z=getchar();
    while(z<'0'||z>'9') y=z,z=getchar();
    while(z>='0'&&z<='9') x=(x<<3)+(x<<1)+(z-'0'),z=getchar();
    return y=='-'?-x:x;
}
int main()
{
    int n,m;
    n=read();
    m=read();
    f[0][0]=1;
    for(int i=1;i<=m;i++)
        for(int j=i;j<=n;j++)
            f[i][j]=f[i][j-i]+f[i-1][j-1];
    cout<<f[m][n]<<endl;
    return 0;
} 
View Code

15、Maximum sum

【題目描述】
對於給定的整數序列A={a1,a2,...,an}A={a1,a2,...,an},找出兩個不重合連續子段,使得兩子段中所有數字的和最大。我們如下定義函數 d(A)d(A):

d(A)=max1≤s1≤t1≤s2≤t2≤n{∑i=s1t1ai+∑j=s2t2aj}
d(A)=max1≤s1≤t1≤s2≤t2≤n{∑i=s1t1ai+∑j=s2t2aj}
我們的目標就是求出d(A)d(A)。

【輸入】
第一行是一個整數T(≤30)T(≤30),代表一共有多少組數據。

接下來是TT組數據。

每組數據的第一行是一個整數,代表數據個數據n(2≤n≤50000)n(2≤n≤50000) ,第二行是nn個整數a1,a2,...,an(|ai|≤10000)a1,a2,...,an(|ai|≤10000)。

【輸出】
輸出一個整數,就是d(A)d(A)的值。

【輸入樣例】
1
10
1 -1 2 2 3 -3 4 -4 5 -5
【輸出樣例】
13
【提示】
就是求最大子段和問題,樣列取2,2,3,−3,42,2,3,−3,4和55,Baidu搜POJ 2479 Maximum sum,可獲得大量經典最大子段和問題的題目解析,本題O(n2)O(n2)算法超時,必須用O(n)O(n)算法。
Maximum sum
#include <bits/stdc++.h>
using namespace std;
const int N=50005; 
int rf[N],lf[N],lmax[N],rmax[N],ans;
int t,n; 
int a[N];
inline int read()
{
    int x=0;char y='*',z=getchar();
    while(z<'0'||z>'9') y=z,z=getchar();
    while(z>='0'&&z<='9') x=(x<<3)+(x<<1)+(z-'0'),z=getchar();
    return y=='-'?-x:x;
}
int main()
{
    t=read();
    while(t--)
    {
        n=read();
        for(int i=1;i<=n;i++)
            a[i]=read();
        lf[1]=a[1];
        rf[n]=a[n];
        lmax[1]=a[1];
        rmax[n]=a[n];
        for(int i=2;i<=n;i++)
            lf[i]=max(a[i],lf[i-1]+a[i]);//從左往右的到i的最大值 
        for(int i=n-1;i>=1;i--)
            rf[i]=max(a[i],rf[i+1]+a[i]);//從右往左到i的最大值;
        for(int i=2;i<=n;i++)
            lmax[i]=max(lmax[i-1],lf[i]);//選擇某一個區間的最大值
        for(int i=n-1;i>=1;i--) 
            rmax[i]=max(rmax[i+1],rf[i]);//選擇某一個區間的最大值
        int ans=a[1];
        for(int i=2;i<=n;i++)
            ans=max(ans,lmax[i-1]+rmax[i]); 
        cout<<ans<<endl;
    }
    return 0;
} 
View Code

16、最長上升公共子序列

經典,結合在一起的,專題專題,但是我這種做法在另一個網站不過

【題目描述】
給定兩個整數序列,寫一個程序求它們的最長上升公共子序列。

當以下條件滿足的時候,我們將長度NN的序列S1,S2,...,SNS1,S2,...,SN 稱為長度為MM的序列A1,A2,...,AMA1,A2,...,AM的上升子序列:

存在1≤i1<i2<...<iN≤M1≤i1<i2<...<iN≤M,使得對所有1≤j≤N1≤j≤N,均有Sj=AijSj=Aij,且對於所有的1≤j<N1≤j<N,均有Sj<Sj+1Sj<Sj+1。

【輸入】
每個序列用兩行表示,第一行是長度M(1≤M≤500)M(1≤M≤500),第二行是該序列的M個整數Ai(−231≤Ai<231)Ai(−231≤Ai<231)
【輸出】
在第一行,輸出兩個序列的最長上升公共子序列的長度LL。在第二行,輸出該子序列。如果有不止一個符合條件的子序列,則輸出任何一個即可。

【輸入樣例】
5
1 4 2 5 -12
4
-12 1 2 4
【輸出樣例】
2
1 4
【提示】
經典算法Baidu搜索,深刻體會。
最長公共子上升序列
#include <bits/stdc++.h>
using namespace std;
const int N=505;
long long a[N],b[N];
int f[N][N],pre[N][N],res[N];
int n,m;
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    cin>>m;
    for(int i=1;i<=m;i++) scanf("%d",&b[i]);
    int ans=0,loci,locj;
    for(int i=1;i<=n;i++)
    {
        int maxx=0,loc=0;
        for(int j=1;j<=m;j++)
        {
            f[i][j]=f[i-1][j];
            pre[i][j]=j;
            if(a[i]>b[j]&&maxx<f[i-1][j])
            {
                maxx=f[i-1][j];
                loc=j;
            }
            else if(a[i]==b[j])
            {
                f[i][j]=maxx+1;
                pre[i][j]=loc;
            }
            if(f[i][j]>ans)
            {
                ans=f[i][j];
                loci=i;
                locj=j;
            }    
        }
    }
    cout<<ans<<endl;
    int num=0;
    while(f[loci][locj])
    {
        while(a[loci]!=b[locj]&&loci) loci--;
        res[++num]=b[locj];
        locj=pre[loci][locj];
    }
    for(int i=num;i>=1;i--)
        cout<<res[i]<<" ";
    cout<<endl;
    return 0;
}
View Code

 


免責聲明!

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



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