今天博主複習了一下棧與遞歸的知識,做了計蒜客平臺的一章習題。下面貼出來和大家交流分享。如有不正之處,請求指教。
蒜頭君喫桃
題目描述
思路分析
設第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棧是什麼定義,下面我寫一下我的解題思路。
思路說明:將進入網頁 回退 前進看成進棧 出棧 問題
- 定義雙棧 前進棧F 回退棧B 便於理解與操作;
- 當操作爲進入p時 一定是合法的輸入 此時應該B.push(p) 清空棧F(因爲這是一次新的瀏覽 之前的Forward記錄都被淹沒了) ,同時輸出B.top();
- 當操作爲回退時,要先判斷棧B的元素個數是否大於1 如果大於1說明可以回退,如果不大於1說明不能回退
3.1如果不能回退 直接輸出Ignore
3.2如果可以回退 B棧的棧頂元素p出棧 B.pop() 同時F.push(p) 輸出B.top()- 當操作是前進時 判斷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;
}