0x02递推与递归

0x02递推与递归

递推者,自小而大,循序渐进;递归者,由上而下,分而治之

例题

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],2Hm[j]+Hm1[ij])

#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;
}



  • 16
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

louisdlee.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值