藍橋杯省賽訓練營——棧與遞歸

今天博主複習了一下棧與遞歸的知識,做了計蒜客平臺的一章習題。下面貼出來和大家交流分享。如有不正之處,請求指教。

蒜頭君喫桃

題目描述在這裏插入圖片描述

思路分析

設第i天還剩下g(n)個桃子 那麼第i-1天還剩下g(n-1)個桃子。

根據遞推關係 有:g(n-1) = g(n) - g(n)/2 - 1 所以 g(n) = 2(g(n-1) + 1)

第一天還剩下1個桃子 所以g(1) = 1

那麼可以寫出遞歸函數:

int64_t GetNumber(int n){
	if(n==1) return 1;
	else return 2*(GetNumber(n-1)+1);
}
int main(){
	int n;cin>>n;
	cout<<GetNumber(n)<<endl;
	return 0;
}

斐波那契數列?

題目描述

在這裏插入圖片描述

思路分析

這一題想都不用想 是最簡單的矩陣快速冪模板題。

重點是求出轉移矩陣。

Matix K = [f(2),f(1)]T(轉置矩陣) T爲轉移矩陣

那麼T(n-2)*K = [f(n), f(n-1)]T

Matix T = {
	a,b,
	1,0
};

所以可以通過快速冪求解

代碼

const int N = 2;
int n,a,b,p;
typedef struct Matix{  //定義N維方陣
	int64_t m[N][N];
}Matix;
Matix temp;//矩陣乘法temp = a*b
Matix Matix_Mutiply(Matix a,Matix b){
	memset(temp.m,0,sizeof(temp.m));
	for(int i=0;i<N;i++){
		for(int j=0;j<N;j++)
			for(int k=0;k<N;k++)
			temp.m[i][j] = (temp.m[i][j] + a.m[i][k]*b.m[k][j]%p)%p;
	}
	return temp;
}
Matix ans,c;//c爲單位陣 ans來代替a
//矩陣快速冪 c = a^n^
Matix Matix_pow(Matix a,int n){
	memset(c.m,0,sizeof(c.m));
	for(int i=0;i<N;i++) c.m[i][i] = 1;
	ans = a;
	while(n){
		if(n&1)  c = Matix_Mutiply(c,ans);
		ans = Matix_Mutiply(ans,ans);
		n = n>>1;
	}
	return c;
}
Matix K = {   //初始的方陣
	1,0,
	1,0
};
int main(){
	cin>>n>>a>>b>>p;
	if(n==1||n==2){  //如果求的是第一項或者第二項
		cout<<1<<endl;
		return 0;
	}
	Matix T = {  //轉移矩陣,也叫變換矩陣T
		a,b,
		1,0
	};
	T = Matix_pow(T,n-2);
	cout<<T.m[0][0]+T.m[0][1]<<endl;//f(n) = 矩陣T的第一行乘以矩陣K的第一列
	return 0;
}

快速冪

題目描述

在這裏插入圖片描述

思路分析

這是整數快速冪的裸題,就不用多說了。

代碼

int64_t fastpow(int64_t x,int64_t n,int64_t p){
	int64_t ans = 1;//賦初值
	if(n==0) return ans;
	while(n){
		if(n&1) ans = (ans*x)%p;//mod
		x = (x*x)%p;//mod
		n = n>>1;
	}
	return ans;
}
int main(){
	int t;cin>>t;
	int64_t x,y,p;
	while(t--){
		cin>>x>>y>>p;
		cout<<fastpow(x,y,p)<<endl;
	}
	return 0;
}

彈簧板

題目描述

在這裏插入圖片描述

思路分析

很顯然,這個最簡單的方法是遞歸,因爲n<=200 不用擔心棧溢出問題。

那麼遞歸怎麼寫呢?

設函數function(int i,int n)表示小球此時在第i個彈簧板上 一共有n個彈簧板的最小移動距離

那麼function(i,n) = min(function(i+a[i],n), function(i+b[i],n)) + 1

而且當i>n的時候 function(i,n) = 0;

int a[205],b[205];
int Getmin(int t,int n){
	if(t>n) return 0;
	return min(Getmin(t+a[t],n),Getmin(t+b[t],n)) + 1;
}

代碼

int a[205],b[205];
int Getmin(int t,int n){
	if(t>n) return 0;
	return min(Getmin(t+a[t],n),Getmin(t+b[t],n)) + 1;
}
int main(){
	int n;cin>>n;

	for(int i=1;i<=n;i++) cin>>a[i];
	for(int i=1;i<=n;i++) cin>>b[i];

	cout<<Getmin(1,n)<<endl;
	return 0;
}

最大公約數

題目描述

在這裏插入圖片描述

思路分析

啥也別說了 默寫gcd()函數、

代碼

int gcm(int a,int b){
	int p = max(a,b);
	int q = min(a,b);
	int temp;
	while(q){
		temp = p%q;
		p = q;
		q = temp;
	}
	return p;
}
int main(){
	int t;cin>>t;
	int x,y;
	while(t--){
		cin>>x>>y;
		cout<<gcm(x,y)<<endl;
	}
	return 0;
}

括號匹配

題目描述

在這裏插入圖片描述

思路分析

這是一道常規的進棧 出棧的題目。難點在於如何對括號進行匹配的保存問題,我採用map的形式保存。

對於一個括號字符串 輸入之後首先判斷他的長度是否爲奇數 如果是奇數 一定不合法匹配

順序掃描這個字符串:

1.如果掃描到是左括號 那麼這個位置的i入棧
2.如果掃描到右括號,左括號序列出棧
2.1如果左括號序列爲空 那麼不合法
2.2出棧一個元素 同時map[i] = j;
3.掃描完畢之後 如果棧非空 那麼不合法
4.只有執行完以上所有條件 這樣的括號字符串才合法 輸出Yes 同時遍歷輸出map

代碼

#include<stack>
#include<map>
string s;//輸入的括號串
int main(){
	stack<int>S;map<int,int>mym;//map表示兩個位置的括號形成匹配
	cin>>s;
	if(s.length()%2!=0){  //如果字符串長度爲奇數 肯定不匹配
		cout<<"No"<<endl;return 0;
	}
	for(int i=0;i<s.length();i++){
		if(s[i] == '(') S.push(i);
		else{
			if(S.empty()) {cout<<"No"<<endl;return 0;} //說明匹配錯誤
			else {
				mym[S.top()+1] = i+1;//字符串下標i是從0開始遍歷的
				S.pop();
			}
		}
	}
	if(!S.empty()){cout<<"No"<<endl;return 0;}
	//接下來的纔是正確的判斷
	cout<<"Yes"<<endl;
	for(map<int,int>::iterator it = mym.begin();it!=mym.end();it++)
		cout<<(it->first)<<" "<<(it->second)<<endl;
	return 0;
}

網頁跳轉

題目描述

在這裏插入圖片描述

樣例輸入:
10
VISIT https://www.jisuanke.com/course/476
VISIT https://www.taobao.com/
BACK
BACK
FORWARD
FORWARD
BACK
VISIT https://www.jisuanke.com/course/429
FORWARD
BACK
樣例輸出
https://www.jisuanke.com/course/476
https://www.taobao.com/
https://www.jisuanke.com/course/476
Ignore
https://www.taobao.com/
Ignore
https://www.jisuanke.com/course/476
https://www.jisuanke.com/course/429
Ignore
https://www.jisuanke.com/course/476

思路分析

博主看到這個題,第一反應是雙棧問題,然後將打開,後退,前進看成是不同的進棧,出棧操作。

這個題的難點在於 對於每一個打開的操作 要清空此時的F棧。

可能大家不知道F棧是什麼定義,下面我寫一下我的解題思路。

思路說明:將進入網頁 回退 前進看成進棧 出棧 問題

  1. 定義雙棧 前進棧F 回退棧B 便於理解與操作;
  2. 當操作爲進入p時 一定是合法的輸入 此時應該B.push(p) 清空棧F(因爲這是一次新的瀏覽 之前的Forward記錄都被淹沒了) ,同時輸出B.top();
  3. 當操作爲回退時,要先判斷棧B的元素個數是否大於1 如果大於1說明可以回退,如果不大於1說明不能回退
    3.1如果不能回退 直接輸出Ignore
    3.2如果可以回退 B棧的棧頂元素p出棧 B.pop() 同時F.push(p) 輸出B.top()
  4. 當操作是前進時 判斷F棧是否爲空 如果爲空說明不能前進 不爲空說明可以前進
    4.1如果不能前進 直接輸出Ignore
    4.2如果可以前進 F棧的棧頂元素p出棧 F.pop(),同時B.push(p) 輸出B.top()

代碼

//思路說明:將進入網頁 回退 前進看成進棧 出棧 問題
//1.定義雙棧 便於理解與操作
//2.當操作爲進入p時 一定是合法的輸入 此時應該B.push(p) 清空棧F(因爲這是一次新的瀏覽 之前的記錄都被淹沒了)同時輸出B.top();
//3.當操作爲回退時,要先判斷棧B的元素個數是否大於1 如果大於1說明可以回退,如果不大於1說明不能回退
//  3.1如果不能回退 直接輸出Ignore
//  3.2如果可以回退 B棧的棧頂元素p出棧 B.pop() 同時F.push(p) 輸出B.top()
//4.當操作是前進時 判斷F棧是否爲空 如果爲空說明不能前進 不爲空說明可以前進
//  4.1如果不能前進 直接輸出Ignore
//  4.2如果可以前進 F棧的棧頂元素p出棧 F.pop(),同時B.push(p) 輸出B.top()
int main(){
	ios::sync_with_stdio(false);//加快cin cout輸入輸出流
	string p,opt;//opt爲操作碼 p爲棧進行交換的碼
	stack<string>B;stack<string>F;//棧B爲back棧  棧F爲FORWARD棧
	int n;cin>>n;
	for(int i=1;i<=n;i++){
		cin>>opt;
		if(opt == "VISIT") {
			cin>>s;
			while(!F.empty()) F.pop();//清空F棧
			B.push(s);
			cout<<s<<endl;//打印back棧的棧頂元素
		}else if(opt == "BACK"){
			if(B.size()<=1){
				//說明不能回退
				cout<<"Ignore"<<endl;
			}else{//可以回退的情況下
				p = B.top();B.pop(); //back棧的棧頂元素出棧並進入Forward棧
				F.push(p);
				cout<<B.top()<<endl;//打印back棧的棧頂元素
			}
		}else if(opt == "FORWARD"){
			if(F.empty()){ //不可以前進
				cout<<"Ignore"<<endl;
			}else{ //可以前進的情況下
				 p = F.top();F.pop();//forward棧的棧頂元素出棧進入back棧
				 B.push(p);
				 cout<<B.top()<<endl;//打印back棧的棧頂元素
			}
		}
	}
	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章