0x02递推与递归
递推者,自小而大,循序渐进;递归者,由上而下,分而治之
文章目录
- 0x02递推与递归
- 例题
- T1:
- T2:
- T3:
- T4:
- T5
- T6
例题
T1:
92. 递归实现指数型枚举 - AcWing题库
思路:每个数分成选或者不选。边界是枚举的数不能超过n。
可以二进制枚举、也可直接递归。
#include<bits/stdc++.h>
using namespace std;
int n;vector<int> v;
void dfs(int pos){if(pos==n+1){for(int i=0;i<v.size();i++){cout<<v[i]<<' ';}cout<<endl;return;}v.push_back(pos);dfs(pos+1);v.pop_back();dfs(pos+1);
}void slove(){cin>>n;dfs(1);
}int main(){slove();
}
T2:
93. 递归实现组合型枚举 - AcWing题库
换汤不换药,修改一下边界即可。
参照上一题代码,当v.size() == m 的时候我们输出 答案
然后,为了防止枚举到超过n的数字,边界就是 n+1
#include<bits/stdc++.h>
using namespace std;int n,m;vector<int> v;void dfs(int pos){if(v.size()==m ){for(int i=0;i<v.size();i++){cout<<v[i]<<' ';}cout<<endl;return;}if(pos==n+1)return;v.push_back(pos);dfs(pos+1);v.pop_back();dfs(pos+1);}void slove(){cin>>n>>m;dfs(1);
}int main(){slove();
}
当然,上面是我写的普通方法,耗时500ms。
蓝书上面在此基础上加了一句话后,其耗时为:
if(v.size()>m or v.size()+n-pos+1<m)return;
这就是剪枝的思想。
我第一个程序,是只有当枚举到n+1的时候,和选了m个数的时候才会终止。
而实际上,如果当前我们选的数大于m,我们直接回溯即可,不必要继续往下再枚举,因为此后的枚举一定是无用的。
如果我们选的数很少,即时后面剩下的数全选了,也达不到m
那么我们直接返回即可,不需要再往下枚举了。
很有意思。
T3:
94. 递归实现排列型枚举 - AcWing题库
换汤换了一点点药。
这题我们在递归函数里面加一个 1~n 的循环结构,使得每次枚举都是从当前能选到的最小的数开始。
如何确定当前能选到的最小的数是多少,我们只要把已经用过的数打个标记就可以了。
所以,其实新东西也就是打标记的想法罢了。然后打完标记要恢复,这就是回溯的思想。
#include<bits/stdc++.h>
using namespace std;
const int N=100;int n;
int flag[N];vector<int> v;void dfs(int pos){if(v.size()==n){for(int i=0;i<v.size();i++)cout<<v[i]<<' ';cout<<endl;return ;}if(pos==n+1)return;for(int i=1;i<=n;i++){if(flag[i]==0){flag[i]=1;v.push_back(i);dfs(pos+1);flag[i]=0;v.pop_back();}}}void slove(){cin>>n;dfs(1);}int main(){slove();
}
T4:
95. 费解的开关 - AcWing题库
代码实现不难,但是思路难想。
有点难度的递推。
其实也就是枚举每一行的操作罢了。
用step代表我们一共操作了多少次。
首先计算如果第一行安灯那么有多少种情况。
用 1 1 1 1 1 代表每个位置的灯全被按过一次
那么总操作次数就是 ( 1<<5 )-1
枚举完第一行的操作后,我们开始枚举第二行。
第二行的操作肯定不是独立的。要在第一行的基础上进行。
如果在第一行操作完之后,第一行还有灭的灯,那么第二行对应的位置就按一次灯。
比如,第一行有两盏灯还是灭的,所以第二行只要操作两次,将第一行全部点亮即可。
此时我们第三行的操作与第二行一样。
一直递推下去,直到第五行操作完毕,此时前四行一定是全亮的,所以我们只要检查第五行有无灭的即可。
然后在此过程中,每次按灯就将step++
如果第五行全亮,我们就打擂台记录最小的step
直到枚举完所有第一行的按灯操作情况。
代码细节多,但是不难实现。
#include<bits/stdc++.h>
using namespace std;
const int N=10;char chess[N][N];int dx[]={-1,1,0,0,0},dy[]={0,0,-1,1,0};void turn(int x,int y){for(int i=0;i<5;i++){int xx=x+dx[i];int yy=y+dy[i];if(xx>=1 and xx<=5 and yy>=1 and yy<=5){chess[xx][yy]='0'+!(chess[xx][yy]-'0'); //改变状态}}}void slove(){int t;cin>>t;while(t--){for(int i=1;i<=5;i++){ //输入棋盘for(int j=1;j<=5;j++){cin>>chess[i][j];}}int ans=1000000;for(int k=0;k<(1<<5);k++){ //状压char temp[N][N];memcpy(temp,chess,sizeof chess); //备份int step=0;for(int i=0;i<5;i++) //每一位if((k>>i)&1){step++;turn(1,i+1);}for(int i=1;i<=4;i++)//递推每一行for(int j=1;j<=5;j++)if(chess[i][j]=='0'){step++;turn(i+1,j);}bool judge=1;for(int j=1;j<=5;j++){//判断是否合法if(chess[5][j]=='0'){judge=0;break;}}if(judge)ans=min(ans,step);memcpy(chess,temp,sizeof temp);}if(ans>6)ans=-1;cout<<ans<<endl;}
}int main(){slove();
}
T5
96. 奇怪的汉诺塔 - AcWing题库
思路:一道很有意思的递归,我们可以由3个柱子的汉诺塔推广到m个柱子的汉诺塔。下面给出推导过程。
设H2[i]为:在盘子只有根柱子能借助的情况下、将i个盘子全部从一根柱子移动到另一根柱子的最少移动次数。
显然,对于两个柱子的汉诺塔,i只能为1,H[1]=1
也就是说,如果盘子只能移动到一个柱子上,那么它只有一种移动方法。
设H3[i] 为:在盘子有两根柱子借助的情况下、将i个盘子全部从一根柱子移动到另一根柱子的最少移动次数。
我们可以先将j个盘子移动到b柱上,此时要移动H3[j]次
(为什么是H3[j]次?因为对于这j个盘子,它可以借助两根空柱子)
然后再将 i-j 个盘子移动到c柱子上,此时只能移动一次。
(因为下面的盘子无法借助b柱子,所以它只能去一根柱子且 i-j 必须是1。)
然后再将j个盘子移动到c柱子上。H3[j](此时这j个盘子能借助空柱子a和柱子c)
所以有递推式子:H3[i]=2*H3[j]+H2[i-j] , (i-j=1)
最终:H3[i] = 2*H3[i-1]+1
同理,如果有四根柱子:
设H4[i]为:在能借用3根柱子的情况下,i个盘子从起点去到目标柱的移动次数。
首先,先选j个盘子,从起点a去起点b暂时呆着:H4[j]
然后将起点的i-j个盘子借助 cd两根空柱子去往d : H3[i-j]
最后将 j个盘子从b移到d,而它能借助的盘子有:acd
H4[j]
所以 H4[i] = 2*H4[j] + H3[i-j]
由于 j 可以选择很多种,而我们要求的是最小移动次数,
所以我们要从 1~i-1 来枚举 j,然后取最小值即可。
那么推广到m个盘子就是:
H m [ i ] = m i n ( H m [ i ] , 2 ∗ H m [ j ] + H m − 1 [ i − j ] ) H_m[i] = min(H_m[i],2*H_m[j]+H_{m-1}[i-j]) Hm[i]=min(Hm[i],2∗Hm[j]+Hm−1[i−j])
#include<bits/stdc++.h>
#include <iostream>
#include<string>
#include<cmath>
#include <vector>
#include<map>
#include<algorithm>
#include<vector>
#include<queue>
using namespace std;
typedef long long ll;
const int N = 1e5 + 7;int H3[20];
int H4[20];int main() {ios::sync_with_stdio(0),cin.tie(0), cout.tie(0);// H3[i]= 把i个盘从a塔移动到c塔 = 先把j个盘从a塔移动到b塔 H[j] + 把i-j个盘从 a塔移动到c塔: 1 + 再把j个盘移动到c塔 H[j]for (int i =1; i <= 12; i++) {for (int j = 0; j <= i-1; j++) {H3[i] = 2 * H3[j] + 1;}}// H4[i] = 把i个盘从a移到d = j:a->b H4[j] + i-j:a->d H3[i-j] + j:b->d H4[j]memset(H4, 0x3f ,sizeof H4);H4[1] = 1;for (int i = 2; i <= 12; i++) {for (int j = 0; j <= i - 1; j++) {H4[i]= min(H4[i],2 * H4[j] + H3[i - j]);}}for (int i = 1; i <= 12; i++)cout << H4[i] << endl;
}
T6
97. 约数之和 - AcWing题库
是一道结合了 数论+分治+递归 的题目
分治部分:求等比数列+快速幂
数论部分:唯一素数分解
递归:求等比数列
大概步骤:
1、写出求递归分治求等比数列的函数
2、快速幂
3、整数分解
等比数列好求:
设 sum(p, k)= p 0 + p 1 + . . . . p k p^0+p^1+....p^k p0+p1+....pk
我们考虑将其分成长度相同的两半:
若k为奇数,那么则有偶数项:
以k=5举例推出一般式子:
s u m ( p , 5 ) = sum(p,5)= sum(p,5)= p 0 + p 1 + p 2 + p 3 + p 4 + p 5 p^0+p^1+p^2+p^3+p^4+p^5 p0+p1+p2+p3+p4+p5
= ( p 0 + p 1 + p 2 ) ( 1 + p 3 ) =(p^0+p^1+p^2)(1+p^3) =(p0+p1+p2)(1+p3)
= s u m ( p , 5 2 ) ∗ ( 1 + p 5 + 1 2 ) =sum(p,\frac{5}2)*(1+p^\frac{5+1}{2}) =sum(p,25)∗(1+p25+1)
一般式子: s u m ( p , k ) = s u m ( p , k 2 ) ∗ ( 1 + p k + 1 2 ) sum(p,k)=sum(p,\frac{k}2)*(1+p^\frac{k+1}{2}) sum(p,k)=sum(p,2k)∗(1+p2k+1)
若n为偶数,那么则有奇数项:
以k=4举例推出一般式子:
s u m ( p , 4 ) = sum(p,4)= sum(p,4)= p 0 + p 1 + p 2 + p 3 + p 4 p^0+p^1+p^2+p^3+p^4 p0+p1+p2+p3+p4
= ( p 0 + p 1 + p 2 ) ( p 2 + 1 ) − p 2 =(p^0+p^1+p^2)(p^2+1)-p^2 =(p0+p1+p2)(p2+1)−p2
一般式子:
= s u m ( p , k 2 ) ∗ ( p k 2 + 1 ) − p k 2 =sum(p,\frac{k}{2})*(p^{\frac{k}{2}}+1)-p^{\frac{k}{2}} =sum(p,2k)∗(p2k+1)−p2k
然后再写快速幂:
int qmi(int a, int k)
{a%=mod;int ans=1;while(k){if(k&1)ans=ans*a%mod;a=a*a%mod;k>>=1;}return ans;
}
所以求等比数列的函数如下:
int sum(int p, int k)
{if (k == 0) return 1;if (k % 2 == 0) return (sum(p,k/2) * (qmi(p,k/2)+1)-qmi(p,k/2)) % mod;return sum(p, k/ 2) % mod * (1+qmi(p,(k+1)/2)) % mod;
}
然后再整数分解就好了:
#include <iostream>using namespace std;const int mod = 9901;int qmi(int a, int k)
{a%=mod;int ans=1;while(k){if(k&1)ans=ans*a%mod;a=a*a%mod;k>>=1;}return ans;
}int sum(int p, int k)
{if (k == 0) return 1;if (k % 2 == 0) return (sum(p,k/2) * (qmi(p,k/2)+1)-qmi(p,k/2)) % mod;return sum(p, k/ 2) % mod * (1+qmi(p,(k+1)/2)) % mod;
}int main()
{int A, B;cin >> A >> B;int res = 1;for(int i=2;i<=A;i++){int k=0;while(A%i==0){k++;A/=i;}res=res*sum(i,k*B)%mod;}if (!A) res = 0;cout << res << endl;return 0;
}