动态规划经典题

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

 

posted @ 2020-04-02 19:02  sumoier  阅读(1242)  评论(0编辑  收藏  举报