NCST第五次蓝桥杯训练赛题解

A、区间和

题目描述

Description
金金最喜欢做有挑战的事情了,比如说求区间最大子段和。
一开始,金金有n个数,第i个数字是ai。
金金又找来了一个新的数字P,并想将这n个数字中恰好一个数字替换成P。要求替换后的最大子段和尽可能大。
金金知道这个题目仍然很简单,所以如果你做不出来,金金就要和你谈一谈了。
注:最大子段和是指在n个数中选择一段区间[L,R](L<=R)使得这段区间对应的数字之和最大。

Input
第一行两个数n,P。
接下来一行n个数a[i]。

Output
一个数表示答案。

Sample Input

5 3

-1 1 -10 1 -1

Sample Output

5

More Info
样例说明:将第三个数变成3后最大子段和为[2,4]。

数据范围:n<=1000,-1000<=ai,P<=1000。

题目讲解

一、动态规划:
对于此题,我们可以创建一个二维数组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位数字,而已经在前面替换过了;

二、枚举:
看数据范围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;
}

B、【蓝桥杯】最大子阵

题目描述

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;
}

C、LIS最长上升子序列

题目描述

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时,237、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);
}

二、改进
上述代码提交后你就会发现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的可能(可以动手写一下)。

D、过河卒

题目描述

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;
}

E、搬砖

题目描述

Description
Sam暑假找了个搬砖的活儿,每天12个小时内有1~8种转可以搬,每种的时间的工资都不一样(如搬第一种砖的时间为0时~4时,工资为5元,搬第二种砖的时间为2时~6时,工资为8元),每次搬砖必须搬完才能搬下一次转(即不可重叠,如上例中搬第一种砖就不能搬第二种转),请你帮Sam拿拿主意,怎样安排可以赚到最多的工资?
Input
输入包含八组(八种砖),每组包含三个整数:搬砖开始时间s1、结束时间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;
}

F、数字三角形

题目描述

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

G、LCS最长公共子序列

题目描述

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;
}

有关动态规划的介绍和题目欢迎查看博主的其它文章(留个赞再走呀(〜 ̄▽ ̄)〜)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
帮我看一下这段代码有什么问题 clear all; fname='G:\CMIP6 data\map_hed\ACCESS-CM2\ssp126.xlsx'; [data]=xlsread(fname); lat = ncread('G:\CMIP6 data\CMIP6_china\Precipitation\ACCESS-CM2 (Australia)\pr_day_ACCESS-CM2_ssp126_r1i1p1f1_gn_20150101-21001231_v20191108.nc','lat'); lon = ncread('G:\CMIP6 data\CMIP6_china\Precipitation\ACCESS-CM2 (Australia)\pr_day_ACCESS-CM2_ssp126_r1i1p1f1_gn_20150101-21001231_v20191108.nc','lon'); % [x,y]=meshgrid(lon,lat); filename4=('E:\XB\xibei\NewFolder\xeibei84.shp'); Shape=shaperead(filename4); Sx=Shape.X;Sy=Shape.Y; R=m_shaperead('E:\XB\xibei\xb_wang');clf; close all a=find(lon>=70 & lon<=140); b=find(lat>=20 & lat<=60); lon_num=length(a);lat_num=length(b); lonn=lon(a,:);latt=lat(b,:); % D=num2cell(data); for i=1 for g=1:length(lon); x=lon(g); for h=1:length(lat); y=lat(h); U=inpolygon(x,y,Sy,Sx); if U==0 data(g,h,:)=nan; end end end end set(gcf,'Position',[0.1 0.1 1500 1000]); [X,Y]=meshgrid(lonn,latt);hold on; m_proj('miller','lon',[70 110],'lat',[30 50]); uu=m_pcolor(X,Y,data'); shading interp; set(uu,'edgecolor','none') % m_grid('linewi',2,'linest','none','xtick',[70:5:115],'ytick',[30:5:50],'fontsize',22,'linewidth',2); % WBGYR % colorbar % h=colorbar('eastoutside'); colormap('autumn'); colorbar; % set(h,'ticks',[-0.1:0.05:0.3],'linewidth',2,'fontsize',22); % caxis([-0.1 0.3]); for v=1:length(R.ncst) m_line(R.ncst{v}(:,1),R.ncst{v}(:,2),'Color','k','Linewidth',0.5); end hold on; % title(' ','fontsize',25); % saveas(figure(1),'spatial.tif') % close all %
06-06

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值