基本算法之递推与递归的简单应用
遞推與遞歸的簡單應用
- 常見的枚舉形式
- 實現指數型枚舉
- DFS (一)
- DFS (二)
- 位運算(一)
- 位運算(二)
- 實現組合型枚舉
- DFS + 剪枝
- 實現排列型枚舉
- DFS
- 費解的開關
- 奇怪的漢諾塔
- 分形之城
常見的枚舉形式
| 多項式 | 循環,遞推 |
| 指數 | 遞歸,位運算 |
| 排列 | 遞歸 ,c++ next_permutation |
| 組合 | 遞歸+剪枝 |
實現指數型枚舉
DFS (一)
思路是沿路徑遞增的形式輸出。
#include <iostream> #include <stdio.h> #include <cstring>using namespace std;int n; int path[100000];void dfs(int stp, int k) {for (int i = 0; i < k; ++i)printf("%d ", path[i]);putchar('\n');if (stp > n)return ;for (int i = stp; i <= n; ++i) {path[k++] = i; //選擇dfs(i + 1, k); k -- ; //回溯,不選擇} }int main() {scanf("%d", &n);dfs(1, 0);return 0; }DFS (二)
void calc(int x){if(x == n+1){for(int i =0;i<chosen.size();++i)printf("%d ",chosen[i]);putchar('\n');return ;}//兩種選擇calc(x+1); //不選chosen.push_back(x); // 選calc(x+1);chosen.pop_back(); // 回溯 }位運算(一)
// 每一個數都有選與不選兩種可能,所以是2的n次方種 for (int i = 0; i < (1 << n); ++i) { for (int j = 0; j < n; ++j)if ((i >> j) & 1) //判斷i的第j為是否選擇cout << j + 1 << " ";cout << '\n'; }位運算(二)
void dfs(int u,int state){if(u == n){for(int i=0;i<n;++i)if(state>>i &1)cout<<i+1<<" ";cout<<endl;return ;}dfs(u+1,state);dfs(u+1,state|1<<u);//將state的第u位置為1}實現組合型枚舉
DFS + 剪枝
#include <iostream> #include <stdio.h> #include <cstring>using namespace std;int n,m; int path[100000];void dfs(int stp, int k) {if(k == m){ // 檢測 放的元素是否達到m個for (int i = 0; i < k; ++i)printf("%d ", path[i]);putchar('\n');}// 剪枝//選的元素個數已經超過m個了,或者即使再選上剩余的所以數也不夠m個if(k >= m || (k+n-stp+1)<m)return ;if (stp > n)return ;for (int i = stp; i <= n; ++i) {path[k++] = i;dfs(i + 1, k);k -- ;} }int main() {scanf("%d%d", &n,&m);dfs(1, 0);return 0; }作者:多元函數 鏈接:https://www.acwing.com/activity/content/code/content/1963737/ 來源:AcWing 著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。實現排列型枚舉
DFS
#include<iostream> #include<stdio.h> #include<cstring> using namespace std;int n; bool vis[10]; int path[10];void dfs(int sp){if(sp == n+1){for(int i=1;i<=n;++i)printf("%d ",path[i]);putchar('\n');return ;}for(int i=1;i<=n;++i){if(!vis[i]){ // 用一個bool 數組記錄一條路徑已經使用過的值,避免重復vis[i] = 1;path[sp] = i; //因為在這里sp就是sp對應的位置應該插入一個數dfs(sp+1);vis[i] = 0; // 回溯}} }int main(){scanf("%d",&n);memset(vis,0,sizeof(vis));dfs(1);return 0; }費解的開關
題目鏈接
難點:
枚舉第一行的所有狀態后如何改變原來的,思路是對于位數為1的就改變,并計數。而因為改變會改變原來的狀態,所以需要用一個數組來保存下原來的狀態在改變之后在恢復回來。在定完第一行后就可以遞推下面的行,行是從0~4的,如果為0就改變,之后就檢驗最后一行是否都是1。
技巧:
對于原來是0的字符變為1,是1的變為0.可以用異或1的方法。
g[xx][yy] ^= 1;代碼:
#include<iostream> #include<stdio.h> #include<cstring> #include<algorithm> const int INF = 987654321; using namespace std;char g[20][20]; int ans; int dx[5] = {0,1,-1,0,0},dy[5] ={0,0,0,1,-1};void turn(int x,int y){for(int i = 0;i<5;++i){int xx = x+dx[i],yy = y+dy[i];if(xx>=0&&xx<5&&yy>=0&&yy<5){g[xx][yy] ^= 1;}} }int work(){for(int k=0;k<(1<<5);++k){int res = 0;char barcup[20][20];memcpy(barcup,g,sizeof g);for(int i=0;i<5;++i)if(k>>i &1){ // 位置為1表明該位置的燈變化res++; //這所以每一步都要計數turn(0,i);}for(int i =0;i<4;++i)for(int j=0;j<5;++j){if(g[i][j] == '0'){res++;turn(i+1,j);}}bool istrue = true;for(int i=0;i<5;++i)if(g[4][i] == '0'){istrue = 0;break;}if(istrue) ans = min(ans,res);memcpy(g,barcup,sizeof g);}if(ans>6)return -1;return ans; }int main(){int t;scanf("%d",&t);while(t--){for(int i=0;i<5;++i)cin>>g[i];ans = INF;ans = work();printf("%d\n",ans);}return 0; }奇怪的漢諾塔
原題鏈接
代碼:
#include<iostream> #include<stdio.h> #include<cstring> using namespace std;const int INF = 98765431; int n; int d[100],f[200];int main(){n = 12;d[1] = 1;for(int i=2;i<=n;++i) // 遞歸算出三個盤子d[i] = 2*d[i-1] + 1;memset(f,0x3f,sizeof f);f[0] = 0;for(int i=1;i<=n;++i)for(int j=0;j<i;++j)// f[j] 是有四個塔的時候,把前j個搭移到一個搭上。// 之后還要移動回來所以是*2// 之后把剩余的i-j個移動的時候只有3個所以是 d[i-j]f[i] = min(f[i],2*f[j]+d[i-j]);for(int i =1;i<=n;++i)printf("%d\n",f[i]);return 0; }分形之城
題目鏈接
遞歸+分治+數學坐標系公式+找規律
遞歸+分治好理解,因為這個題目中最顯著的特點就是,不斷地重復旋轉復制,也就是NN級城市,可以由44個N?1N?1級城市構造,因此我們每次可以不斷地分形N?1N?1級,將問題范圍不斷地縮小即可
這道題目的數學坐標公式,其實一共有兩個,一個是高中的數學函數,旋轉,這是一個難點,其實可以通過找規律,求解,第二公式則是歐幾里得距離公式。(x1?x2)2?(y1?y2)2??????????????????√(x1?x2)2?(y1?y2)2
最難的就是如何旋轉這個正方形 找規律。
總的來說這道題目數學知識較多,考察畫圖能力,解法自然,數據毒瘤,相信可以給你的NOIP一個有利的一腳。
- 左上角:我們可以發現,左上角的N?1N?1級矩陣其實就是等級為N?1N?1,也就是上一個矩陣,順時針旋轉90°90°,那么既然如此的話,我們就可以綜合yxc老師上課所講的公式(補充:也就是旋轉矩陣,屬于大學的線性代數內容),得出轉移后的矩陣中的一點坐標從(x,y)(x,y)變為(y,x)(y,x)
- 左下角:同左上角,它則是逆時針旋轉90°90°而且還要水平翻轉,也即是沿著XX軸對稱,原本逆時針后為(y,?x)(y,?x),然后要對稱,xx坐標不變,yy坐標取反,所以坐標為(?y,?x)(?y,?x) 也就是所謂的(2×len?1?y,len?1?x)(2×len?1?y,len?1?x) 最難理解的坐標,具體可以畫圖理解
- 右上角和右下角:通過N=2N=2級圖發現,其實和N=1N=1是一樣的,并沒有旋轉,只是平移,則右上角坐標為(x,y+len)(x,y+len),右下角坐標為(x+len,y+len)(x+len,y+len)
總的來說以上四種轉移,都可以通過畫圖理解
還有本題數據毒瘤,四舍五入最好是double類型,而且整數類型一定要是long long,否則容易WA!
題解出處:https://www.acwing.com/solution/content/814/
小技巧:
坐標(x,y) 順時針旋轉一定度數后得到的坐標公式是:
(x,y)∣cos?θsin?θ?sin?θcos?θ∣(x,y) \left| \begin{matrix} \cos\theta & \sin\theta \\ -\sin\theta & \cos\theta \\ \end{matrix} \right| (x,y)∣∣∣∣?cosθ?sinθ?sinθcosθ?∣∣∣∣?
代碼:
#include <iostream> #include <cstdio> #include <cstring> #include <cmath> using namespace std; #define ll long long #define PLL pair<ll,ll> PLL calc(ll n,ll m) {if (n==0)return make_pair(0,0);ll len=1LL<<(n-1),cnt=1LL<<(2*n-2);PLL pos=calc(n-1,m%cnt);ll x=pos.first,y=pos.second;ll z=m/cnt;if (z==0)return make_pair(y,x);if (z==1)return make_pair(x,y+len);if (z==2)return make_pair(x+len,y+len);return make_pair(2*len-1-y,len-1-x); } int main() {//ios::sync_with_stdio(false);int t;scanf("%d",&t);while(t--){ll n,a,b;scanf("%lld%lld%lld",&n,&a,&b);PLL x=calc(n,a-1);PLL y=calc(n,b-1);ll dx=x.first-y.first,dy=x.second-y.second;double ans=(sqrt(double(dx*dx+dy*dy))*10);printf("%0.lf\n",ans);}return 0; }總結
以上是生活随笔為你收集整理的基本算法之递推与递归的简单应用的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 算法训练之STL使用汇总
- 下一篇: 个人场第十场题解