蓝桥杯学习笔记整理

文章目录

    • 蓝桥杯学习笔记整理
      • 0. 必会单词
      • 0.2 dev必备操作
          • 1.调试
            • 数组(一二维)
            • 单步调试能够进入函数内部
          • 2.快捷键
      • 1. 快速幂运算
          • 涉及知识点
      • 2. 矩阵的快速幂
      • 3.无根树转有根数
      • 4.字符串与数字相互转化
        • (1)字符串转数字
          • c_str() + atoi()
            • c_str()
            • atoi
            • 自定义函数转化(可定义位置和长度)
        • (2)数字转字符串
      • 5.全排列总结(无重复元素)
          • 自写递归回溯
            • 三步走
          • 调用next_permutation()函数
            • 注意点
            • 类型一:数组
            • 类型二:字符串
      • 6.暴力枚举优化
            • 减少枚举范围
            • 减少枚举变量:利用其他变量表示
            • 用空间换时间
      • 7.快速排序
          • 双指针:两头往中间跑与标尺比较
          • 数字乱码排序计数
            • (1)连续从1~N,一次交换任意两个数
            • (2)连续从k~N,一次交换任意两个数
            • (3) 随机N个数,一次交换任意两个数
            • (4)连续从1~N或k ~N,一次交换相邻两个数
            • (5)随机N个数,一次交换相邻两个数
      • 8.控制输出位数
      • 9.二维数组连通性检测(dfs)
          • (1)多方向选择但走一个型:
            • 代码框架:
            • 移动小技巧:
          • (2)四周扩散型:
          • (3)二者区别:四周扩散型的没有出口,所以它的效果就是四周遍历。而第一种,我们只要加上相应的出口和判断条件,虽然它最后也是全部可能都走了,但是我们把只要有出口,加上判断条件,就能判断那些路线是你要的。
      • 10 辗转相除法求最大公约数
          • 原理:
          • 代码:
      • 11 C(a,b)求法
      • 12.二分查找
          • 模板:
          • (1)例一 分巧克力
      • 13.1深搜的递归做法(求多路线最优)
          • 模板
          • 例题:
            • 题目
            • 图解:
            • 代码:
            • 缺点:如果是编程题目,不建议这样做,运行时间会过长
      • 13.2 深搜的递归加记忆(多路线最优解)
            • 题目
            • 图解:
            • 代码:

蓝桥杯学习笔记整理

0. 必会单词

 false  true
 
 continue break 
 
 defaultswitch 语句)
 
 (set<int>) :: iterator it = s.begin();it<s.end();
 
 insert()// (set输入)

0.2 dev必备操作

1.调试
数组(一二维)
单步调试能够进入函数内部
2.快捷键
注释: Ctrl+/
向下复制一行:Ctrl+E
删除一行:Ctrl+D
整体代码缩进对齐: Ctrl+Shift+A
编辑运行:win+F11
选择全部:Ctrl+A

*(&a[1])@n //n为长度

1. 快速幂运算

涉及知识点

(1)位与运算:先将n和1转化为4*8个bit的二进制,如何1代表true,0代表false,然后就是正常的与运算了

(2)有符号右移运算:本质上是转化为2进制补码然后右移,符号位补齐。虽然效果跟 n = n/2^1一样,但是这样比较快,因为这是直接对补码进行操作,少了转化等过程

(3)其实就是将一个数n 的k次幂转化为n2*x1*n2*x2
例如求**410 = 48*404240
10 的二进制为1 0 1 0

求x的n次幂并对m取余数

typedef long long ll;
ll quick_pow(ll x,ll n,ll m){
	ll res = 1;
	while(n > 0){
		if(n & 1)	res = res * x % m;
		x = x * x % m;
		n >>= 1;//相当于n=n/2^1
	}
	return res;
}

2020 8 15 更新

2. 矩阵的快速幂

//定义全1矩阵 
struct M{
	LL a[6][6];
	M(){
		for(int i=0;i<6;i++){
			for(int j=0;j<6;j++){
				a[i][j]=1;
			}
		}
	}
};//这里有个分号

//矩阵乘法
//三重for循环,前两重是确定生成新矩阵的位置的,第三重是配合前两重做出乘法 
M mMultiply(M m1,M m2){
	M ans;
	for(int i=0;i<6;i++){
		for(int j=0;j<6;j++){
			ans.a[i][j]=0;//因为我这里定义的是全1矩阵,所以需要初始化为0
			for(int k=0;k<6;k++){
				ans.a[i][j]=(ans.a[i][j]+m1.a[i][k]*m2.a[k][j])%MOD;
			}
		}
	}
	return ans; 
}
//矩阵M的k次幂
M mPow(M m,int k){
	M ans;
//初始化ans为单位矩阵
	for(int i=0;i<6;i++){
		for(int j=0;j<6;j++){
			if(i==j) 
				ans.a[i][j]=1;
			else
				ans.a[i][j]=0;
		} 
	}
	
	while(k!=0){
		if((k & 1)==1) {
			ans = mMultiply(ans,m);
		}
		m = mMultiply(m,m);
		k >>= 1;
	}
	return ans;
 
}

2020 8 16

3.无根树转有根数

题目:

输入一个n个节点的无根树的各条边,并指定一个根节点,要求把该树转化为有根树,输出各个节点的父亲编号。
蓝桥杯学习笔记整理_第1张图片

#include 
#include 
using namespace std;
 
const int MAXN = 1000;
int n, p[MAXN];//p[i]代表i的父亲
vector<int> G[MAXN];
 
void dfs(int u, int fa) {   //递归转化为以u为根的子树,u的父亲为fa
	int d = G[u].size();        //节点u的相邻点的个数
	for(int i = 0; i < d; ++i) {    //循环遍历跟这个节点相连接的d个节点。
		int v = G[u][i];       //节点u的第i个相邻点v
		if(fa != v) dfs(v, p[v] = u);  //把v的父亲节点设为u,然后递归转化为以v为根的子树
		//一定要判断v是否和其父亲节点相等!
	}
}
 
int main() {
	cin >> n;
	for(int i = 0; i < n-1; i++) {   //输入n-1条边
		int u, v;
		cin >> u >> v;
		G[u].push_back(v);
		G[v].push_back(u);
	}
	int root;   
	cin >> root;    //指定根节点。
	p[root] = -1;   //设定根节点的父亲节点为-1,代表根节点没有父亲节点。
	dfs(root, -1);
	for(int i = 0; i < n; ++i) {
		cout << p[i] << endl;
	}
	return 0;
}

4.字符串与数字相互转化

(1)字符串转数字

c_str() + atoi()
c_str()

它是string类对象的成员函数,主要就是把string类型我们转化为char类型,方便我们调用char的函数

注意点:
注意:一定要使用strcpy()函数 等来操作方法c_str()返回的指针
比如:最好不要这样:
char* c;
string s=“1234”;
c = s.c_str(); //c最后指向的内容是垃圾,因为s对象被析构,其内容被处理

应该这样用:
char c[20];
string s=“1234”;
strcpy(c,s.c_str());
这样才不会出错,c_str()返回的是一个临时指针,不能对其进行操作

再举个例子
c_str() 以 char* 形式传回 string 内含字符串
如果一个函数要求char*参数,可以使用c_str()方法:
string s = “Hello World!”;
printf("%s", s.c_str());
//输出 “Hello World!”

atoi

该函数的格式为
int atoi(const char* str)

所以这就是我们为什么上面要结合c_str()的原因
配合例题学习

题目描述
100  可以表示为带分数的形式:100  =  3  +  69258  /  714。 
还可以表示为:100  =  82  +  3546  /  197。 
注意特征:带分数中,数字1~9分别出现且只出现一次(不包含0)。 
类似这样的带分数,10011  种表示法。 
输入
从标准输入读入一个正整数N  (N< 1000*1000) 
输出
程序输出该数字用数码1~9不重复不遗漏地组成带分数表示的全部种数。 
注意:不要求输出每个表示,只统计有多少表示法! 
样例输入
100  
样例输出
11

解题思路: 由题意可知1-9都要出现,且只能出现一次。自然想到用全排列来做,只需要加入一些判断即可(最后面的完整改进后完整代码有一些细节讲解)


注意事项: 在这个网站可以运行但是在蓝桥杯网站运可能会超时

参考代码(1:在这个网站可以运行,但是在蓝桥杯网站会超时,但是也可以学习一些一些函数及字符串处理

#include
#include
#include
using namespace std;
 
//这种方法超时,因为多次用了substr()来截取字符串,每次都要拷贝,和扫描
//过于耗时
int main(){
    int n;
    int ans = 0;
    cin>>n;
    std::string s = "123456789"; 
    do {
            for(int i=0;i<=7;i++){
                string a=s.substr(0,i); 
                int inta = atoi(a.c_str());
                if(inta >= n) break;
                 
                for (int j=1;j<=9-i-1;j++){
                    string b = s.substr(i,j);
                    string c = s.substr(i+j,9);
                    int intb = atoi(b.c_str());
                    int intc = atoi(c.c_str());
                    if(intb%intc==0&&inta+intb/intc==n) ans++;
                }
                 
            }
        } while(std::next_permutation(s.begin(),s.end())) ;
         
    cout<<ans;
    return 0;
}
自定义函数转化(可定义位置和长度)
题目描述
100  可以表示为带分数的形式:100  =  3  +  69258  /  714。 
还可以表示为:100  =  82  +  3546  /  197。 
注意特征:带分数中,数字1~9分别出现且只出现一次(不包含0)。 
类似这样的带分数,10011  种表示法。 
输入
从标准输入读入一个正整数N  (N< 1000*1000) 
输出
程序输出该数字用数码1~9不重复不遗漏地组成带分数表示的全部种数。 
注意:不要求输出每个表示,只统计有多少表示法! 
样例输入
100  
样例输出
11

解题思路: 由题意可知1-9都要出现,且只能出现一次。自然想到用全排列来做,只需要加入一些判断即可(最后面的完整改进后完整代码有一些细节讲解)


注意事项: 在这个网站可以运行但是在蓝桥杯网站运可能会超时

参考代码(1:在这个网站可以运行,但是在蓝桥杯网站会超时,但是也可以学习一些一些函数及字符串处理

改进代码:因为我们的atoi函数没有选择字符串长度就是只转化某个位置的字符串,所以我们需要自己定义一个转化函数。

//pos代表串开始的位置,len代表长度(包含pos) 
int parse(const char *arr,int pos,int len){
    int ans = 0;
    int t = 1;
    for(int i=pos-1+len-1;i>=pos-1;i--){//因为下标-1,因为包含pos-1
        ans+=(arr[i]-'0')*t;
        t*=10;
    }
    return ans;
}


改进后完整代码:

#include
#include
#include
using namespace std;
 
int parse(const char *arr,int pos,int len){
    int ans = 0;
    int t = 1;
    for(int i=pos+len-1-1;i>=pos-1;i--){
        ans+=(arr[i]-'0')*t;
        t*=10;
    }
    return ans;
}
int main(){
    int n;
    int ans = 0;
    cin>>n;
    std::string s = "123456789"; 
    do {
            const char *str = s.c_str();// 由于s.c_str()这个函数返回的是const的类型,
                                        // 所以我们上面定义的函数的参数也要是const
            for(int i=0;i<=7;i++){
                int inta = parse(str,1,i); // +号前的数字 
                if(inta >= n) break; // 如果单inta就>=n,那后面就不用遍历了,进入下一种排列
                 
                for (int j=1;j<=9-i-1;j++){
                int intb=parse(str,i+1,j); // +和/中间的数字
                int intc=parse(str,i+j+1,9-i-j); // / 后面的数字
                if(intb%intc==0&&inta+intb/intc==n) ans++;
                }
                 
            }
        } while(std::next_permutation(s.begin(),s.end())) ;
         
    cout<<ans;
    return 0;
}

(2)数字转字符串

void i2s(int i,string &s){
	stringstream ss;
	ss<<i;
	ss>>s;
}

具体例子:

//string -> double/int
#include 
#include 
 
using namespace std;
 
int main()  
{  
    double  dVal;    
    int     iVal;
    string  str;
    stringstream ss;
    
    // string -> double
    str = "123.456789";  
    ss << str;
    ss >> dVal;
    cout << "dVal: " << dVal << endl;
 
    // string -> int
    str = "654321";  
    ss.clear();
    ss << str;
    ss >> iVal;
    cout << "iVal: " << iVal << endl;  
        
    return 0;  
} 

在这里插入图片描述

5.全排列总结(无重复元素)

自写递归回溯
三步走
  1. 替换
  2. 进一步递归
  3. 回溯
    代码如下:
void f(int k){
	if(k==9){//一种排列已经生成 
		if(check())
			ans++;
	}
	
	for(int i=k;i<9;i++){
		{ int t=a[i]; a[i]=a[k]; a[k]=t; }//替换 
		f(k+1);//递归 
		{ int t=a[i]; a[i]=a[k]; a[k]=t; } //回溯
		//这里的递归,本质上是先纵再橫,我们只看从k到k+1,回溯是为了消除上一次替换的效果,然后尝试下一种。 
	}
	
}
调用next_permutation()函数
注意点

传入的要提前排好序

类型一:数组
#include
#include
using namespace std;
int ans=0;
int a[9]={1,2,3,4,5,6,7,8,9}; 
 bool check(){
 int A=a[0];
 int b=a[1];
 int c=a[2];
 int def=a[3]*100+a[4]*10 +a[5];
 int ghi=a[6]*100+a[7]*10+a[8];
 if(b*ghi+def*c==(10-A)*c*ghi)
 		return true;
 	else
 		return false;
 } 
int main(){
	do{
		if(check()){
			ans++;
		}
		
	}while(next_permutation(a,a+9));
	cout<<ans;
	return 0; 
} 
类型二:字符串

重点就是

  1. 排好序
  2. s.begin() s.end()
#include
#include
#include
using namespace std;
 
int parse(const char *arr,int pos,int len){
    int ans = 0;
    int t = 1;
    for(int i=pos+len-1-1;i>=pos-1;i--){
        ans+=(arr[i]-'0')*t;
        t*=10;
    }
    return ans;
}
int main(){
    int n;
    int ans = 0;
    cin>>n;
    std::string s = "123456789"; 
    do {
            const char *str = s.c_str();// 由于s.c_str()这个函数返回的是const的类型,
                                        // 所以我们上面定义的函数的参数也要是const
            for(int i=0;i<=7;i++){
                int inta = parse(str,1,i); // +号前的数字 
                if(inta >= n) break; // 如果单inta就>=n,那后面就不用遍历了,进入下一种排列
                 
                for (int j=1;j<=9-i-1;j++){
                int intb=parse(str,i+1,j); // +和/中间的数字
                int intc=parse(str,i+j+1,9-i-j); // / 后面的数字
                if(intb%intc==0&&inta+intb/intc==n) ans++;
                }
                 
            }
        } while(std::next_permutation(s.begin(),s.end())) ;
         
    cout<<ans;
    return 0;
}

6.暴力枚举优化

减少枚举范围
减少枚举变量:利用其他变量表示
用空间换时间

参考:四平方和

https://blog.csdn.net/Hgg657046415/article/details/108096692

7.快速排序

双指针:两头往中间跑与标尺比较

思路:
先选一个“标尺”,(每次都是左端点,最后在交换,左端点又变成了新的分界点)
用它把整个队列过一遍筛子,
以保证:其左边的元素都不大于它,其右边的元素都不小于它。
这样,排序问题就被分割为两个子区间。
再分别对子区间排序就可以了。
蓝桥杯学习笔记整理_第2张图片

void swap(int a[], int i, int j)//交换 
{
	int t = a[i];
	a[i] = a[j];
	a[j] = t;
}
//定义标尺并把大于标尺的放在右侧,小于的放在左侧 
int partition(int a[], int p, int r)
{
    int i = p;//将开头赋值给 i 
    int j = r + 1;//将结尾+1赋值给j 
    int x = a[p];//标尺:每次都以左端点为标尺
    while(1){
        while(i<r && a[++i]<x);
        while(a[--j]>x);
        if(i>=j) break;
        swap(a,i,j);
    }
	swap(a,p,j); 
    return j;
}

void quicksort(int a[], int p, int r)//p到r是需要排的范围 
{
    if(p<r){
        int q = partition(a,p,r);//q才是标尺位置 
        quicksort(a,p,q-1);
        quicksort(a,q+1,r);
    }
}
数字乱码排序计数
(1)连续从1~N,一次交换任意两个数

做法:
类似于贪心,我们就是每一步都是最优解

  1. 从头开始扫描
  2. 用数组存贮,对应下标应该存对应数值
  3. 如果没有就找到该数值进行交换

代码:

#include 
#include
using namespace std;

int ans=0;
int n;
int a[10001];

//找到并放回x在a[]中的下标
int pos(int x){
	for(int i=1;i<=n;i++){
		if(a[i]==x) return i;
	}
	return -1;
}
void swap(int i,int j){
	int t=a[i];
	a[i]=a[j];
	a[j]=t;
}

int main(){
	cin>>n; 
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	for(int i=1;i<n;i++){
		if(a[i]==i) continue;
		else{//pos(i)为找到i在a[]中的下标
			swap(i,pos(i));
			ans++; 
			}
		}
	
//	for(int i=1;i<=n;i++){
//		cout<
//	}
//	cout<
	cout<<ans;
	return 0;
	
} 
(2)连续从k~N,一次交换任意两个数

做法:找到min ( a[k] ),并每个数减去(k-1),范围就会回到1~N-(k-1)
然后就是(1)了

(3) 随机N个数,一次交换任意两个数

计数排序:对数值范围特别大的不友好,但是对数值不大,但是数量多的特别优秀。

(4)连续从1~N或k ~N,一次交换相邻两个数

全部逆序个数

(5)随机N个数,一次交换相邻两个数

全部逆序个数

8.控制输出位数

cout<<setiosflags(ios::fixed)<<setprecision(2)<<area;
//cout<<设置 ios 标志(ios::确定的)<<设置精度(精度值)<<输出内容;

9.二维数组连通性检测(dfs)

(1)多方向选择但走一个型:

对应题目:地宫取宝
https://blog.csdn.net/Hgg657046415/article/details/108181254

代码框架:
void dfs(参数1...){

//控制范围合理
if(超出递归范围) return;

if(判断是否到出口即可以达到要求的位置){
	if(达到要求) ans++
	dfs();
}
//上面是对这一层递归的判断

//递归部分
dfs();
dfs();
dfs();
}

移动小技巧:
//先定义一个数组
int direction[4][2]={{-1,0},
					 {1,0},
					 {0,-1},
					 {0,1}};
//实施方式
//四个方向的递归,(只看一个的话)
	for(int i=0;i<4;i++){
		int nx=x+direction[i][0];
		int ny=y+direction[i][1];
		
		//下面看情况而定
		if(nx<0||nx>6||ny<0||ny>6) continue;
		if(!sign[nx][ny]){
			dfs(nx,ny);
		}
	}
(2)四周扩散型:

对应题目:B_2016_07_剪邮票
https://blog.csdn.net/Hgg657046415/article/details/108180359

void dfs(int b[3][4],int i,int j){
	b[i][j]=0;
	if((i-1)>=0&&b[i-1][j]==1) dfs(b,i-1,j);
	if((i+1)<=2&&b[i+1][j]==1) dfs(b,i+1,j);
	if((j-1)>=0&&b[i][j-1]==1) dfs(b,i,j-1);
	if((j+1)<=3&&b[i][j+1]==1) dfs(b,i,j+1);
	
}
(3)二者区别:四周扩散型的没有出口,所以它的效果就是四周遍历。而第一种,我们只要加上相应的出口和判断条件,虽然它最后也是全部可能都走了,但是我们把只要有出口,加上判断条件,就能判断那些路线是你要的。

10 辗转相除法求最大公约数

原理:

辗转相除法又名广义欧几里得除法,是用来求解两个数的最大公约数的最佳算法之一。

算法原理:若a除以b的余数为r , 则有 (a , b) = ( b ,r ) ((a,b)表示a和b的最大公约数)

例:169与48的最大公约数求解过程

169 = 48 * 3 + 25 —— (169 , 48) = (48 , 25)

48 = 25 * 1 + 13 ——(48 , 25) = (25 , 13)

25 = 13 * 1 + 12

13 = 12 * 1 + 1

12 = 1 * 12 + 0 ——(12 , 1 ) = (1,0)= 1

故最大公约数为 1

代码:
#include
using namespace std;
 
int gcd(int a,int b)       //定义函数gcd 计算 
{
	if(a%b==0)  
        return b;          // 如果a能被整除b 则b为最大公约数 
    else                   
        return gcd(b,a%b); //如果不能整除,则将b作为新的被除数,a作为新的除数,    
} 
 
int main()                 //定义主函数 
{
	int a,b;               //定义变量 a,b 
	cin>>a>>b;             //输入 
	cout<<gcd(a,b);        //输出a,b的最大公约数 
	return 0;
	
}

11 C(a,b)求法

12.二分查找

模板:
int r=最大值;
int l=1;//最小值
while(r<=l){
mid=(l+r)/2;
...
操作区
...
if(满足条件){
l=mid+1;//也可以是r=mid-1;
}
else{
r=mid-1;
}
}
(1)例一 分巧克力
/*
标题: 分巧克力
    儿童节那天有K位小朋友到小明家做客。小明拿出了珍藏的巧克力招待小朋友们。
    小明一共有N块巧克力,其中第i块是Hi x Wi的方格组成的长方形。
    为了公平起见,小明需要从这 N 块巧克力中切出K块巧克力分给小朋友们。
	切出的巧克力需要满足:
    1. 形状是正方形,边长是整数  
    2. 大小相同  
例如一块6x5的巧克力可以切出6块2x2的巧克力或者2块3x3的巧克力。
当然小朋友们都希望得到的巧克力尽可能大,你能帮小Hi计算出最大的边长是多少么?
输入
第一行包含两个整数N和K。(1 <= N, K <= 100000)  
以下N行每行包含两个整数Hi和Wi。(1 <= Hi, Wi <= 100000) 
输入保证每位小朋友至少能获得一块1x1的巧克力。   
输出
输出切出的正方形巧克力最大可能的边长。
样例输入:
2 10  
6 5  
5 6  
样例输出:
2
*/
#include
#include
using namespace std;
int main(){
	int N,K;
	int a[100000][2];
	cin>>N>>K;
	for(int i=0;i<N;i++){
		cin>>a[i][0]>>a[i][1];
	}
	int S=0;
	for(int i=0;i<N;i++){
		S+=(a[i][0]*a[i][1]);
	}
	
	int r = abs(S/K)+1;
	int l=1;
	int ans=0; //记录最优的mid(最大的) 
	
	while(l<=r){
		int mid=(l+r)/2;
		int cut=0;
// 每块巧克力按照mid来切割		
		for(int i=0;i<N;i++){
			cut+=(a[i][0]/mid)*(a[i][1]/mid);
		}
		if(cut>=K){
			l=mid+1;//mid可以,继续尝试最优解
			ans=mid; //记录本次最优解 
		}else{
			r=mid-1;
		} 
		
	}
	cout<<ans<<endl;
	return 0; 	 
}

13.1深搜的递归做法(求多路线最优)

模板
int f(int x,int y){
	if(x==n||y==n){//在数组中x代表“哪一维”是纵向
		return a[x][y];
	}
	else
		a[x][y]+max(f(x+1,y),f(x+1,y+1));
}
例题:
题目
有一个层数为n(n<=1000)的数字三角形。现有一只蚂蚁从顶层开始向下走,每走下一级,可向左下方向或右下方向走。求走到底层后它所经过数字的总和的最大值。
【输入格式】
第一个整数为n,一下n行为各层的数字。
【输出格式】
一个整数,即最大值。
【输入样例 】
5
1
6 3
8 2 6
2 1 6 5
3 2 4 7 6
【输出样例】
23
【样例说明】
最大值=1+3+6+6+7=23
图解:

蓝桥杯学习笔记整理_第3张图片

代码:
#include
#include
using namespace std;
int a[1001][1001],n;
int f(int x,int y)// found the max number in a
{
	if(x==n||y==n)
	{
		return a[x][y];
	}
	else{
		return a[x][y]+max(f(x+1,y),f(x+1,y+1));
	}
}
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=i;j++)
		{
			cin>>a[i][j];
		}
	}
	cout<<f(1,1);//begin from (1,1)
 }
缺点:如果是编程题目,不建议这样做,运行时间会过长

13.2 深搜的递归加记忆(多路线最优解)

题目
有一个层数为n(n<=1000)的数字三角形。现有一只蚂蚁从顶层开始向下走,每走下一级,可向左下方向或右下方向走。求走到底层后它所经过数字的总和的最大值。
【输入格式】
第一个整数为n,一下n行为各层的数字。
【输出格式】
一个整数,即最大值。
【输入样例 】
5
1
6 3
8 2 6
2 1 6 5
3 2 4 7 6
【输出样例】
23
【样例说明】
最大值=1+3+6+6+7=23
图解:

蓝桥杯学习笔记整理_第4张图片
我们可以看到其实在第三层的f(3,2)重复计算了两次
蓝桥杯学习笔记整理_第5张图片
其实f(x,y)函数的意义就是,以(x,y)为顶点往下遍历所有路线中值最大的路线总值。

所以我们用一个二维数组记录一下,在上面仅仅4层一共15个f(x,y)的计算中就可以减少4次。

代码:
did[x][y]是记录以(x,y)为起点往下遍历的最大值路线所对对应的值
模板:
int f(int x,int y) {
	if(x==n-1) {
		return a[x][y];
	} 
	if(did[x][y]){
		return did[x][y];
	}else {
		did[x][y]=a[x][y]+max(f(x+1,y),f(x+1,y+1));
		return did[x][y];
	}
	
}
#include
#include
using namespace std;
int a[1001][1001],did[1001][1001],n;
int f(int x,int y) {
	if(x==n-1) {
		return a[x][y];
	} 
	if(did[x][y]){
		return did[x][y];
	}else {
		did[x][y]=a[x][y]+max(f(x+1,y),f(x+1,y+1));
		return did[x][y];
	}
	
}
int main() {
	cin>>n;
	for(int i=0; i<=n; i++) {
		for(int j=0; j<=i; j++) {
			cin>>a[i][j];
		}
	}
	cout<<f(0,0);//begin from (0,0)
}

你可能感兴趣的:(备战蓝桥杯,算法)