藍橋杯 第一講 遞迴與遞推
阿新 • • 發佈:2022-01-07
92. 遞迴實現指數型列舉
dfs遞迴做法
#include<iostream> using namespace std; const int N = 20; int n; bool st[N]; //1~N每個數的狀態陣列:0表示未選擇,1表示已選擇 void dfs(int u) { if(u >= n) //遞迴出口:深搜到第n+1位,列印該方案 { for(int i=0;i < n;i++) { if(st[i])//選擇該數就列印 { cout<<i+1<<" "; } } puts(""); return; } st[u] = true; //第一個分支:選擇該數 dfs(u+1); st[u] = false; //dfs必須恢復狀態 dfs(u+1);//第二個分支:不選該數 } int main() { cin>>n; dfs(0); return 0; }
二進位制數位迴圈列舉做法
#include<iostream> using namespace std; const int N = 20; int n; int a[N] = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20}; int main() { cin>>n; for(int i=0;i < 1<<n;i++) { int t = i; for(int j=0;j<n;j++) { if(t%2 == 1) cout<<a[j]<<" "; t = t>>1; } puts(""); } return 0; }
94. 遞迴實現排列型列舉
O(n!*n)
#include<iostream> using namespace std; const int N = 10; bool st[N];//1~N中每個數是否被使用 int n; int a[N];//方案 void dfs(int u)//列舉到第幾位 { if(u >= n) //已經列舉完最後一位 { for(int i=0;i<n;i++) { cout<<a[i]<<" ";//輸出方案 } puts(""); return; } for(int i=1;i<=n;i++) //從小到大列舉每個數 { if(!st[i]) //沒有被用過 { st[i] = true; //標記使用 a[u] = i;//記錄到方案中 dfs(u+1);//搜尋下一位 st[i] = false; //下一層搜尋回來,恢復狀態 } } } int main() { cin>>n; dfs(0); return 0; }
93. 遞迴實現組合型列舉
y總寫法
#include<iostream>
using namespace std;
const int N = 30;
int n,m;
int way[N];
void dfs(int u,int st) //u是第幾位,st是從哪個數開始列舉
{
if(n - st < m - u) return;//剪枝:當前剩餘待選的數的個數小於空位數
if(u == m + 1)
{
for(int i=1;i<=m;i++)
{
cout<<way[i]<<" ";
}
puts("");
}
way[u] = st;
dfs(u+1,st+1);
dfs(u+1,st)
}
int main()
{
cin>>n>>m;
dfs(1,1);
}
我的寫法
#include<iostream>
using namespace std;
const int N = 25;
int n,m;
bool st[N];
void dfs(int x,int t) //x表示選擇的數,t表示已選擇的數的個數
{
if(t >= m) //選夠了,就輸出方案
{
for(int i=1;i<=n;i++)
{
if(st[i]) cout<<i<<" ";
}
puts("");
return;
}
if(x > n) return;//選擇的數不大於n
st[x] = true;//選擇x
dfs(x+1,t+1);//保證每次新加的數大於前一個數,升序
st[x] = false;//不選擇x
dfs(x+1,t);
}
int main()
{
cin>>n>>m;
dfs(1,0);
return 0;
}
1209. 帶分數
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 20;
bool st[N];
bool backup[N];
int n,ans;
bool check(int a,int c)
{
int b = n*c - a*c;//根據題意求出b的大小
if(a==0 || b==0 || c == 0) return false;//a,b,c不允許任何一個為0
memcpy(backup,st,sizeof st);//check函式需要單獨的判重陣列
while(b)//列舉b的每一位
{
int a = b % 10;
b /= 10;
if(a == 0 || backup[a] == true) return false;//若為0或者已經使用過的數,則失敗
backup[a] = true;
}
for(int i=1;i<=9;i++)
{
if(!backup[i]) return false;//若有未使用過的數,則失敗
}
return true;
}
void dfs_c(int u,int a,int c)
{
if(u >= 10) return;//1~9所有數字用完了
if(check(a,c)) ans++;//檢查當前a和c是否滿足條件
for(int i=1;i<=9;i++)
{
if(!st[i])
{
st[i] = true;
dfs_c(u+1,a,c * 10 + i);//注意:第二個引數要求出c的具體大小
st[i] = false;
}
}
}
void dfs_a(int u,int a) //u表示已經使用了多少數字,a表示當前a的大小
{
if(a >= n) return;//遞迴出口
if(a) dfs_c(u,a,0);//若a不為0,就列舉一下c
for(int i=1;i<=9;i++)
{
if(!st[i])
{
st[i] = true;
dfs_a(u+1,a*10+i);//注意:第二個引數要求出a的具體大小
st[i] = false;//涉及回溯,必須恢復現場
}
}
}
int main()
{
cin>>n;
dfs_a(0,0);//a必須從0開始,若從1開始,沒有標記已使用
cout<<ans<<endl;
return 0;
}
95. 費解的開關
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 6;
int T;
char g[N][N];
char backup[N][N];
int dx[5] = { 0,-1,1,0,0 };
int dy[5] = { 0,0,0,-1,1 };
void turn(int x, int y)
{
for (int i = 0; i < 5; i++)
{
int a = x + dx[i];
int b = y + dy[i];
if (a < 0 || a >= 5 || b < 0 || b >= 5) continue;
if (backup[a][b] == '1') backup[a][b] = '0';
else backup[a][b] = '1';
}
}
int main()
{
cin >> T;
while (T--)
{
int ans = 10;
for (int i = 0; i < 5; i++)
{
cin >> g[i];
}
for (int i = 0; i < 32; i++)//以位運算方式列舉第一行操作的所有方案
{
int cnt = 0;
memcpy(backup, g, sizeof g);//先備份,對backup操作
for (int j = 0; j < 5; j++)//獲取每一位,是1為操作開關,否則不操作開關
{
if ((i >> j) & 1 == 1)
{
cnt++;
turn(0, j);
}
}
for (int x = 0; x < 4; x++) //每一行開關的操作由前一行狀態唯一確定
{
for (int y = 0; y < 5; y++)
{
if (backup[x][y] == '0')
{
cnt++;
turn(x + 1, y);
}
}
}
bool f = true;
for (int y = 0; y < 5; y++) //最後判斷最後一行是否全亮,若沒有,則說明此方案不可取
{
if (backup[4][y] == '0')
{
f = false;
break;
}
}
if (f) ans = min(ans, cnt); //方案可取,則進行比較,選擇操作次數最小的
}
if (ans > 6) ans = -1;//若最小次數都大於6,則說明無解
cout << ans << endl;
}
return 0;
}
116. 飛行員兄弟
1.位運算列舉法
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
typedef pair<int, int> PII;
vector<PII>tmp,res;
const int N = 5;
char g[N][N],backup[N][N];
void turn(int x,int y)
{
for(int i=0;i<4;i++)
{
if(backup[x][i] == '+') backup[x][i] = '-';
else backup[x][i] = '+';
if(backup[i][y] == '+') backup[i][y] = '-';
else backup[i][y] = '+';
}
if(backup[x][y] == '+') backup[x][y] = '-';
else backup[x][y] = '+';
return;
}
int main()
{
for (int i = 0; i < 4; i ++ )
{
cin>>g[i];
}
for(int op = 0;op< 1<<16;op++)
{
vector<PII>tmp;
memcpy(backup,g,sizeof g);
for(int i=0;i<16;i++)
{
if(op>>i & 1 == 1)
{
int x = i/4;
int y = i%4;
tmp.push_back({x,y});
turn(x,y);
}
}
bool f = true;
for(int i=0;i<4;i++)
{
for(int j=0;j<4;j++)
{
if(backup[i][j] == '+')
{
f = false;
break;
}
}
if(!f) break;
}
if(f)
{
if(res.empty() || tmp.size() < res.size()) res = tmp;
}
}
int len = res.size();
cout<<len<<endl;
for(auto t:res)
{
cout<<t.first + 1<<" "<<t.second + 1<<endl;
}
return 0;
}
2.dfs列舉法
#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
char g[5][5];
typedef pair<int,int>PII;
vector<PII>tmp,res;
void turn(int x,int y)
{
for(int i=0;i<4;i++)
{
if(g[x][i] == '+') g[x][i] = '-';
else g[x][i] = '+';
}
for(int i=0;i<4;i++)
{
if(g[i][y] == '+') g[i][y] = '-';
else g[i][y] = '+';
}
if(g[x][y] == '+') g[x][y] = '-'; //x.y處開關操作3次等於操作1次
else g[x][y] = '+';
return;
}
void dfs(int x,int y)
{
if(x == 3 && y == 4)//遞迴出口:列舉完所有開關
{
bool f = true;
for(int i=0;i<4;i++) //檢查所有開關是否為開,否則說明此方案不可行
{
for(int j=0;j<4;j++)
{
if(g[i][j] == '+')
{
f = false;
break;
}
if(!f) break;
}
}
if(f)//若是,則進行比較,選擇操作次數較小的
{
if(res.empty()||tmp.size() < res.size())
{
res = tmp;
}
}
return;
}
if(y==4)//換行
{
y = 0;
x++;
}
turn(x,y);
tmp.push_back({x,y});
dfs(x,y+1);//選擇操作此開關
turn(x,y);//恢復現場
tmp.pop_back();
dfs(x,y+1);//選擇不操作此開關
}
int main()
{
for(int i=0;i<4;i++) cin>>g[i];
dfs(0,0);
int len = res.size();
cout<<len<<endl;
for(int i=0;i<len;i++)
{
cout<<res[i].first + 1<<" "<<res[i].second + 1<<endl;
}
return 0;
}