Description
金金最喜欢作有挑战的事情了,好比说求区间最大子段和。
一开始,金金有n个数,第i个数字是ai。
金金又找来了一个新的数字P,并想将这n个数字中刚好一个数字替换成P。要求替换后的最大子段和尽量大。
金金知道这个题目仍然很简单,因此若是你作不出来,金金就要和你谈一谈了。
注:最大子段和是指在n个数中选择一段区间[L,R](L<=R)使得这段区间对应的数字之和最大。ios
Input
第一行两个数n,P。
接下来一行n个数a[i]。c++
Output
一个数表示答案。算法
Sample Input数组
5 3ide
-1 1 -10 1 -1优化
Sample Outputspa
5指针
More Info
样例说明:将第三个数变成3后最大子段和为[2,4]。code
数据范围:n<=1000,-1000<=ai,P<=1000。blog
1、动态规划:
对于此题,咱们能够建立一个二维数组dp[N][2],dp[i][0]存储不替换数字的前i个数字区间和的最大值,dp[i][1]存储替换数字后的前i个数字区间和的最大值,dp[i][1]的值用dp[i][0]判断并更新。
不替换数字的状态转移方程:
dp[i][0]=max(dp[i-1][0]+x,x);
替换数字后的状态转移方程:
dp[i][1]=max(p,max(dp[i-1][0]+p,dp[i-1][1]+x));
dp[i-1][0]+p表示替换第i位数字;
dp[i-1][1]+x表示不替换第i位数字,而已经在前面替换过了;
2、枚举:
看数据范围n小于1000,因此n^2复杂度的程序能够经过程序。
1.不断枚举替换的数字,从第一个数字枚举替换到最后一个。
2.对于寻找最大的区间和有如下的约束:
1)从第一个数字开始累加,一旦这个累加和小于零,则舍弃这段区间,由于将它放入,只会产生负效果。
2)寻找到的最大值须要更新
if(i==j) sum+=p; //替换a[i] else sum+=a[j]; if(sum<0) sum=0; //舍弃区间 ans=max(ans,sum); //更新
动态规划:
#include <iostream> #define N 1005 #define inf -99999999 using namespace std; int n,p,ans; int dp[N][2]={0}; int main() { cin>>n>>p; int x,ans=inf; for(int i=1;i<=n;i++){ cin>>x; dp[i][0]=max(dp[i-1][0]+x,x); //dp[i][0]:不更换数字前i个数字的最大区间和 dp[i][1]=max(p,max(dp[i-1][0]+p,dp[i-1][1]+x)); //dp[i][1]:更换数字后前i个数字的最大区间和 ans=max(ans,dp[i][1]); //更新ans } cout<<ans; return 0; /*不替换数字的区间和:for(int i=1;i<=n;i++){ dp[i]=max(a[i],dp[i-1]+a[i]); ans=max(ans,dp[i]); }*/ }
枚举:
#include <iostream> using namespace std; int n,p,a[1005],ans=-9999999; int main() { cin>>n>>p; for(int i=1;i<=n;i++) cin>>a[i]; for(int i=1;i<=n;i++){ //从第1个数开始枚举被替换的数字 int sum=0; for(int j=1;j<=n;j++){ //计算用p替换掉a[i]后的区间和 if(i==j) sum+=p; //替换a[i] else sum+=a[j]; if(sum<0) sum=0; //舍弃区间 ans=max(ans,sum); //更新 } } cout<<ans<<endl; return 0; }
Description
给定一个n*m的矩阵A,求A中的一个非空子矩阵,使这个子矩阵中的元素和最大。
其中,A的子矩阵指在A中行和列均连续的一块。
Input
输入的第一行包含两个整数n, m,分别表示矩阵A的行数和列数。(1 ≤ n, m ≤ 500)
接下来n行,每行m个整数,表示矩阵A。
Output
输出一行,包含一个整数,表示A中最大的子矩阵中的元素和。
Sample Input
3 3
-1 -4 3
3 4 -1
-5 -2 8
Sample Output
10
More Info
WA?你考虑矩阵内全为负的状况了么
咱们都知道在一维状况求最大连续子序列很好求:
for(int i=1;i<=n;i++){ dp[i]=max(a[i],dp[i-1]+a[i]); ans=max(ans,dp[i]); }
那么二维最大连续区间和怎么求呢?咱们不妨换个思路,把二维转换成一维,由于仔细观察咱们就会发现,对于一个固定的连续二维矩阵,竖着的一列确定是都取的,不会出现这一列取两个,旁边一列取三个。这样咱们就能够把每列的值相加成一个值,从而把多行转换为一行,也就是把二维转换成一维。例如:
0 5 -1 9
5 4 -3 -1
8 8 -4 -3
转换成:
13 17 -8 5
对于上述3*4的矩阵,要求它的最大子阵,能够进行行和列枚举:
(1)一行k列(1<=k<=4):
即求每一行长度为k的一维数组最大连续子序列。(不必定以第一列的数开头)
(2)二行k列:
即求把连续两行压缩成一行后长度为k的一维数组最大连续子序列。
例如:(0,5,-1 )和 (5 ,4, -3 )压缩成(5,9,-4)
(3):三行k列:
即求把连续三行压缩成一行后长度为k的一维数组最大连续子序列。
这样,这题实际上就转变成了一维状况求最大连续子序列。
#include <iostream> #include <cstring> //memset要用到 using namespace std; int dp[505]={0},n,m,ans=-99999999; int a[505][505]={0},b[505][505]; void DP(int j) //一维状态下求最大连续子序列 { for(int i=1;i<=m;i++){ dp[i]=max(b[j][i],dp[i-1]+b[j][i]); ans=max(ans,dp[i]); } } int main() { cin>>n>>m; for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) scanf("%d",&a[i][j]); //二维数组转化为一维 for(int i=1;i<=n;i++){ //从第i行开始 memset(b,0,sizeof(b));//清空b数组 for(int j=i;j<=n;j++){ //到第j行结束 for(int k=1;k<=m;k++) //枚举列 b[j][k]=a[j][k]+b[j-1][k]; //b[j][k]表示i~j行的k列相加的值 DP(j); } } cout<<ans<<endl; return 0; }
Description
给定一长度为n的数列,数列中的每一个数都在1~100000之间
请在不改变原数列顺序的前提下,从中取出必定数量的整数(不须要连续),并使这些整数构成单调上升序列。
输出这类单调上升序列的最大长度。
Input
输入包括两行,第一行为n,表明数列的长度。(1 ≤ n ≤ 100000)
第二行为n个整数。
Output
输出这类单调上升序列的最大长度
Sample Input
5
3 1 5 4 6
Sample Output
3
More Info
对于样例的解释:356,156,146 都可组成长度为3的上升子序列
1.找子问题:
“求序列的前n个元素的最长上升子序列”是一个子问题,但这个方法是行不通的,由于其不具备无后效性:
设F(n)=x (前n个元素的最长上升子序列为x),但在前n个元素中,可能有多个上升子序列的值为x(结尾元素不必定是同一个,如2 3 7 6 5 6 2,当n=5时,23七、236 、235)。有的序列的最后一个元素比第n+1个元素小,则能造成一个更长的子序列;有的序列的最后一个元素大于等于第n+1个元素,没法造成更长上升子序列。则当走到第n+1时,F(n+1)的值不只和F(n)有关,还和前n个元素中的多个最长上升子序列的最后一个元素有关,和之前所了解的“只和上一个状态的值有关,与是如何到达上一个状态的方式无关”相矛盾。换句话说,F(n+1)的值应当只与F(n)的值有关。
换一个方向考虑,把子问题改为“求以a[k] (1<=k<=N)为终点的最长上升子序列的长度”,把全部的F(n) (以第n个元素结尾的最长上升子序列长度)都求出来后,只须要找出其中最大的就是答案。
2.肯定状态:
如上文所说,"状态"就是以a[k] (1<=k<=N)为终点的最长上升子序列。
3.肯定初始状态的值:
显然,F(1)=1;可是此处应当把全部的F初始值都设为1;否则轮到一个k,前1~k-1个元素没有比其小的,当在求后面的状态的值时,轮到k时其F(k)应当为1;
4.肯定状态转移方程:
dp[i]=max(dp[i],dp[j]+1);
代码:
#include <iostream> #include <cstdio> #include <algorithm> using namespace std; int a[100005],dp[100005]; int main() { int n; cin>>n; for(int i=1;i<=n;i++) { scanf("%d",&a[i]); dp[i]=1; } for(int i=2;i<=n;i++) { for(int j=1;j<i;j++) { if(a[j]<a[i]) dp[i]=max(dp[i],dp[j]+1); } } cout<<*max_element(dp+1,dp+n+1); }
2、改进
上述代码提交后你就会发现TLE了-_-||(由于时间复杂度为n^2) .
那么如何改呢。这里用了非DP方法,经过辅助数组s[],结合数列自己特性求解,s[]的长度即为答案。
操做:逐个处理a[]里面的数字(1)若是a[i]比辅助数组s[]的最后一个数更大,s[]长度加一,把a[i]添加到s[]结尾;(2)若是a[i]比s[]最后一个数小,就用lower_bound()二分查找s[]中第一个大于等于a[i]的位置,替换之。
初始化:s[1]=a[1];tot=1;
#include<cstdio> #include<iostream> #include<cstring> #include<algorithm> using namespace std; int n,a[100010]; int s[100010],tot=0,tmp; int main(){ cin>>n; for(int i=1;i<=n;i++) scanf("%d",&a[i]); s[++tot]=a[1]; for(int i=2;i<=n;i++){ if(a[i]>s[tot])s[++tot]=a[i]; else{ tmp=lower_bound(s+1,s+tot+1,a[i])-s;//STL的经常使用算法,二分查找s中第一个大于等于a[i]的数,复杂度为O(logn)(s[]必须有序) s[tmp]=a[i]; //用a[i]替换 } } printf("%d\n",tot); return 0; }
解释:
为何这样能行呢?对于操做(1),若是a[i]比辅助数组s[]的最后一个数更大,s[]长度加一,把a[i]添加到s[]结尾,很好理解。(2)"若是a[i]比s[]最后一个数小,就用lower_bound()二分查找s[]中第一个大于等于a[i]的位置,替换之。"首先,这个操做并不影响s[]的长度,即LIS的长度;其次,对于a[]中后面未处理的数字,可能不少都比s[]的最后一位小,可是有可能序列更长,这里的替换就给后面的更小的数字留下了机会,就不会错过造成更长LIS的可能(能够动手写一下)。
Description
大三老学长ZHB平时喜欢边晒晒太阳边下象棋养养老,有一天他忽然想到了这么一个问题:
棋盘上AA点有一个过河卒,须要走到目标BB点。卒行走的规则:能够向下、或者向右。同时在棋盘上CC点有一个对方的马,该马所在的点和全部跳跃一步可达的点称为对方马的控制点。所以称之为“马拦过河卒”。
棋盘用坐标表示,AA点(0, 0)(0,0)、BB点(n, m)(n,m)(nn, mm为不超过2020的整数),一样马的位置坐标是须要给出的(假设马的位置不会在距离棋盘边缘两格范围内)。
如今ZHB学长要求你计算出卒从AA点可以到达BB点的路径的条数,假设马的位置是固定不动的,并非卒走一步马走一步。
Input
一行四个数据,分别表示BB点坐标和马的坐标。
Output
一个数据,表示全部的路径条数。
Sample Input
6 6 3 3
Sample Output
6
More Info
结果可能很大!
本题稍加分析就能发现,要到达棋盘上的一个点,只能从左边或上面过来,因此,根据加法原理,到达某一点的路径数目,就等于到达其相邻的上点和左点的路径数目之和,所以咱们可使用逐列(或逐行)递推的方法来求出从起点到终点的路径数目。
障碍点(马的控制点)也彻底适用,只要将到达该点的路径数目设置为0便可。
状态转移方程:dp[i][j]=dp[i-1][j]+dp[i][j-1]
dp[n][m]即为答案。
(PS:本身动手画一下会很清楚)
#include <bits/stdc++.h> using namespace std; int n,m,p,q; int x[9]={0,-2,-2,-1,-1,1,1,2,2},y[9]={0,-1,1,-2,2,-2,2,-1,1}; //标记马及其八个控制点的位置 long long dp[21][21]={0},a[21][21]={0}; int main() { int i,j; cin>>n>>m>>p>>q; for(i=0;i<9;i++) { if(p+x[i]>=0 && p+x[i]<=n && q+y[i]>=0 && q+y[i]<=m) a[p+x[i]][q+y[i]]=1; //标记 } dp[0][0]=1; //初始化 for(i=1;i<=n;i++) //为何要把dp[0][i]和dp[i][0]设为1?本身动手画一下就知道了 { if(a[i][0]==0) dp[i][0]=1; else break; } for(i=1;i<=m;i++) { if(a[0][i]==0) dp[0][i]=1; else break; } for(i=1;i<=n;i++) for(j=1;j<=m;j++) { if(a[i][j]==0) dp[i][j]=dp[i-1][j]+dp[i][j-1]; else dp[i][j]=0; } cout<<dp[n][m]; return 0; }
Description
Sam暑假找了个搬砖的活儿,天天12个小时内有1~8种转能够搬,每种的时间的工资都不同(如搬第一种砖的时间为0时~4时,工资为5元,搬第二种砖的时间为2时~6时,工资为8元),每次搬砖必须搬完才能搬下一次转(即不可重叠,如上例中搬第一种砖就不能搬第二种转),请你帮Sam拿拿主意,怎样安排能够赚到最多的工资?
Input
输入包含八组(八种砖),每组包含三个整数:搬砖开始时间s一、结束时间s2与工资sal。(0 <= s1 < 11 , 0 < s2 <= 11 , 1 <= sal <= 10)。
Output
Sam12小时内最多能够赚到的工资。
Sample Input
3 5 1
1 4 5
0 6 8
4 7 4
3 8 6
5 9 3
6 10 2
8 11 4
Sample Output
13
这题有不少解法,这里我用的是区间dp。
用f[i][j]表示从i时刻搬到j时刻的最大工资,f[0][11]即为答案。
(1)枚举区间长度(1~11);
(2)枚举开始时刻并由长度肯定结束时刻;
(3)逐个枚举中间时刻k并更新最大值。
关系式:f[i][j]=max(f[i][j],f[i][k]+f[k][j]);
时间复杂度:O(n^3)。(想要优化的能够自行查阅资料)
#include <iostream> #include <algorithm> using namespace std; int f[13][13]={0}; int main() { int x,y,v; for(int i=1;i<=8;i++){ cin>>x>>y>>v; f[x][y]=v; } for(int len=1;len<12;len++) //i和j之间距离,即区间长度 for(int i=0;i<12-len;i++){ //从第i时刻开始 int j=i+len; //到第j时结束 for(int k=i;k<j;k++) f[i][j]=max(f[i][j],f[i][k]+f[k][j]); } cout<<f[0][11]<<endl; return 0; }
Description
给定一个高度为 n (1 ≤ n ≤ 100)的“数字三角形”,其中第 i 行(1<=i<=n)有 i 个数。
1
2 3
4 5 6
7 8 9 10
初始时,你站在“数字三角形”的顶部,即第一行的惟一一个数上。每次移动,你能够选择移动到当前位置正下方或者当前位置右下方的位置上。即若是你在 (i,j)(表示你在第i行从左往右数第j个数上,下同),你能够选择移动到 (i+1,j) 或 (i+1,j+1)。
你想让你通过的全部位置(包括起点和终点)的数字总和最大。求这个最大值。
Input
第一行一个正整数 n,表示数字三角形的大小。
第 2 行到第 n+1 行,第 i+1 行为 i 个用空格隔开的非负整数,描述数字三角形的第 i 行。
Output
一行一个整数,表示通过路径上数的最大总和。
Sample Input
4
1
2 3
4 5 6
7 8 9 10
Sample Output
20
More Info
对样例解释
不停地向右下走便可
从下往上推,二维数组对应位置存储由下往上推到当前位置时更大的那个结果。
能够看到,对应位置的结果只与上一步推的结果和当前位置的初始值有关。由此可得状态转移方程:maxSum[i][j]=max(maxSum[i+1][j],maxSum[i+1][j+1])+D[i][j]。
#include <iostream> #include <algorithm> using namespace std; #define MAX 1001 int D[MAX][MAX],n; int maxSum[MAX][MAX]; int main() { int i,j; cin>>n; for(i=1;i<=n;i++) for(j=1;j<=i;j++) cin>>D[i][j]; for(i=1;i<=n;i++) maxsum[n][i]=D[n][i]; //底部初始化 for(i=n-1;i>=1;i--) for(j=1;j<=i;j++) maxSum[i][j]=max(maxSum[i+1][j],maxSum[i+1][j+1])+D[i][j]; cout<<maxSum[1][1]<<endl; return 0; }
PS:滚动数组优化和指针优化欢迎查看博主的另外一篇文章:洛谷[P1216] 数字三角形 Number Triangles
Description
给定两个序列 X={x1,x2,…,xm} 和 Y={y1,y2,…,yn},找出X和Y的最长公共子序列。
Input
输入数据有多组,每组有两行 ,每行为一个长度不超过500的字符串(输入全是小写英文字母),表示序列X和Y。
Output
每组输出一行,表示所求得的最长公共子序列的长度,若不存在公共子序列,则输出0。
Sample Input
abcbdab
bdcaba
Sample Output
4
用dp[i][j]表示字符串S1的前i个字符和字符串S2的前j个字符的最长公共子序列长度。
(1)当s1[i]==s2[j]时,dp[i][j]=dp[i-1][j-1]+1;
(2)当s1[i]!=s2[j]时,此时须要求解【1】:s1的前i-1个字符和s2的前j个字符的最长公共子序列,即dp[i-1][j];【2】:s1的前i个字符和s2的前j-1个字符的最长公共子序列,即dp[i-1][j];取其中最大值做为dp[i][j]的值。
#include <iostream> #include <cstring> using namespace std; string s1,s2; int dp[505][505]; //dp[i][j]表示字符串S1的前i个字符和字符串S2的前j个字符的最长公共子序列长度 int main() { while(cin>>s1>>s2){ memset(dp,0,sizeof(dp)); //由于多组输入,每一次都要清空dp[][] for(int i=1;i<=s1.length();i++) for(int j=1;j<=s2.length();j++){ if(s1[i-1]==s2[j-1]) dp[i][j]=dp[i-1][j-1]+1; else { dp[i][j]=max(dp[i-1][j],dp[i][j-1]); } } cout<<dp[s1.length()][s2.length()]<<endl; } return 0; }
有关动态规划的介绍和题目欢迎查看博主的其它文章(留个赞再走呀(〜 ̄▽ ̄)〜)