动态规划经典题
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; }
合并石子,加强(圆形)求最大值和最小值
思路,形成了一个环之后,用两倍的大小来模拟环形
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; }
还有一种思路:%n
2、乘积最大
思路:主要是输入的问题,将每个位置的数保存下来,用a[i][i]表示,用数组a[i][j]表示位置i到j的值是多少;
【题目描述】 今年是国际数学联盟确定的“2000——世界数学年”,又恰逢我国著名数学家华罗庚先生诞辰90周年。在华罗庚先生的家乡江苏金坛,组织了一场别开生面的数学智力竞赛的活动,你的一个好朋友XZ也有幸得以参加。活动中,主持人给所有参加活动的选手出了这样一道题目: 设有一个长度为N的数字串,要求选手使用K个乘号将它分成K+1个部分,找出一种分法,使得这K+1个部分的乘积最大。 同时,为了帮助选手能够正确理解题意,主持人还举了如下的一个例子: 有一个数字串:312, 当N=3,K=1时会有以下两种分法: 1)3*12=36 2)31*2=62 这时,符合题目要求的结果是:31*2=62。 现在,请你帮助你的好朋友XZ设计一个程序,求得正确的答案。 【输入】 第一行共有2个自然数N,K(6≤N≤10,1≤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; }
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; }
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; }
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≤50−50≤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≤50−50≤Aij≤50,其中 AijAij 是花束 ii 在花瓶 jj 中的美学值。 【输入】 第一行包含两个数:F,VF,V。 随后的FF行中,每行包含VV个整数,AijAij 即为输入文件中第(i+1i+1)行中的第jj个数。 【输出】 第一行是程序所产生摆放方式的美学值。 第二行必须用FF个数表示摆放方式,即该行的第KK个数表示花束K所在的花瓶的编号。 【输入样例】 3 5 7 23 –5 –24 16 5 21 -4 10 23 -21 5 -4 -20 20 【输出样例】 53 2 4 5
#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; }
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; }
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; }
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; }
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; }
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; }
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; }
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,下面三种分法被认为是相同的。 1,1,5; 1,5,1; 5,1,1; 问有多少种不同的分法。 输出一个整数,即不同的分法。 Input 两个整数n,k( 6 < n <= 200,2 <= k <= 6),中间用单个空格隔开。 Output 一个整数,即不同的分法。 Sample Input 7 3 Sample Output 4 Hint 四种分法为:1,1,5;1,2,4;1,3,3;2,2,3。
//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; }
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)算法。
#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; }
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; }