算法竞赛进阶指南0x 递归与递推

知识点

递归:当问题存在,C->B->A这样的拓扑序的情况下,我们即可使用递归,把A直接丢给递归,在处理A之前先处理B,在处理B之前先处理C

递推:和递归相反,递推先处理C再B最后A

递归实现指数型枚举

从 1~n 这 n 个整数中随机选取任意多个,输出所有可能的选择方案。

输入格式
输入一个整数n。

输出格式
每行输出一种方案。

同一行内的数必须升序排列,相邻两个数用恰好1个空格隔开。

对于没有选任何数的方案,输出空行。

本题有自定义校验器(SPJ),各行(不同方案)之间的顺序任意。

数据范围
1≤n≤15
输入样例:
3
输出样例:

3
2
2 3
1
1 3
1 2
1 2 3

#include<iostream>
#include<algorithm>
#include<cstring>

using namespace std;

int n;

void dfs(int pos,int k)
{
    if(k==n)
    {
        for(int i=0;i<n;i++)
            if(pos>>i&1)
                cout<<i+1<<" ";
        puts("");
        return;
    }   
    dfs(pos,k+1);//不选
    dfs(pos|(1<<k),k+1);//选
}
int main(){
    //freopen("data.in","r",stdin);
    //freopen("data.out","w",stdout);
    cin>>n;
    dfs(0,0);


    return 0;
}

这题没什么好说的,基操~ . ~

递归实现组合型枚举

从 1~n 这 n 个整数中随机选出 m 个,输出所有可能的选择方案。

输入格式
两个整数 n,m ,在同一行用空格隔开。

输出格式
按照从小到大的顺序输出所有方案,每行1个。

首先,同一行内的数升序排列,相邻两个数用一个空格隔开。

其次,对于两个不同的行,对应下标的数一一比较,字典序较小的排在前面(例如1 3 5 7排在1 3 6 8前面)。

数据范围
n>0 ,
0≤m≤n ,
n+(n−m)≤25
输入样例:
5 3
输出样例:
1 2 3
1 2 4
1 2 5
1 3 4
1 3 5
1 4 5
2 3 4
2 3 5
2 4 5
3 4 5

这题就是从上题代码,再剪枝就行了,来看代码。

#include<iostream>
#include<algorithm>
#include<cstring>

using namespace std;

int n,m;

void dfs(int pos,int k,int cnt)
{
    if(cnt>m||(cnt+(n-k)<m)) return ;
    if(k==n)
    {
        for(int i=0;i<n;i++)
            if(pos>>i&1)
                cout<<i+1<<" ";
        puts("");
        return;
    }   
    dfs(pos|(1<<k),k+1,cnt+1);//选
    dfs(pos,k+1,cnt);//不选
}
int main(){
    //freopen("data.in","r",stdin);
    //freopen("data.out","w",stdout);
    cin>>n>>m;
    dfs(0,0,0);


    return 0;
}

递归实现排列型枚举

把 1~n 这 n 个整数排成一行后随机打乱顺序,输出所有可能的次序。

输入格式
一个整数n。

输出格式
按照从小到大的顺序输出所有方案,每行1个。

首先,同一行相邻两个数用一个空格隔开。

其次,对于两个不同的行,对应下标的数一一比较,字典序较小的排在前面。

数据范围
1≤n≤9
输入样例:
3
输出样例:
1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1

这题呢,我推荐大家调用库(大佬请无视)

#include<iostream>
#include<cstring>
#include<algorithm>

using namespace std;

const int N=15;

int a[N];

int main(){
    //freopen("data.in","r",stdin);
    //freopen("data.out","w",stdout);
    int n;
    cin>>n;
    for(int i=1;i<=n;i++) a[i]=i;
    do{
        for(int i=1;i<=n;i++) cout<<a[i]<<" ";
        puts("");
    }while(next_permutation(a+1,a+n+1));



    return 0;
}

费解的开关

你玩过“拉灯”游戏吗?25盏灯排成一个5x5的方形。每一个灯都有一个开关,游戏者可以改变它的状态。每一步,游戏者可以改变某一个灯的状态。游戏者改变一个灯的状态会产生连锁反应:和这个灯上下左右相邻的灯也要相应地改变其状态。

我们用数字“1”表示一盏开着的灯,用数字“0”表示关着的灯。下面这种状态

10111
01101
10111
10000
11011
在改变了最左上角的灯的状态后将变成:

01111
11101
10111
10000
11011
再改变它正中间的灯后状态将变成:

01111
11001
11001
10100
11011
给定一些游戏的初始状态,编写程序判断游戏者是否可能在6步以内使所有的灯都变亮。

输入格式
第一行输入正整数n,代表数据中共有n个待解决的游戏初始状态。

以下若干行数据分为n组,每组数据有5行,每行5个字符。每组数据描述了一个游戏的初始状态。各组数据间用一个空行分隔。

输出格式
一共输出n行数据,每行有一个小于等于6的整数,它表示对于输入数据中对应的游戏状态最少需要几步才能使所有灯变亮。

对于某一个游戏初始状态,若6步以内无法使所有灯变亮,则输出“-1”。

数据范围
0<n≤500
输入样例:
3
00111
01011
10001
11010
11100

11101
11101
11110
11111
11111

01111
11111
11111
11111
11111
输出样例:

3
2
-1

来看代码

#include<iostream>
#include<cstring>
#include<algorithm>

using namespace std;

const int N=31;

char g[N][N],t[N][N];
int dx[5]={-1,1,0,0,0},dy[5]={0,0,-1,1,0};

void touch(int x,int y){
    for(int i=0;i<5;i++)
    {
        int xx=x+dx[i],yy=y+dy[i];
        g[xx][yy]^=1;
    }

}
int main(){
    //freopen("data.in","r",stdin);
    //freopen("data.out","w",stdout);
    int T;
    cin>>T;
    while(T--)
    {
        for(int i=1;i<=5;i++)
            scanf("%s",g[i]+1);
        int res=7;
        memcpy(t,g,sizeof t);
        for(int i=0;i<1<<5;i++)
        {
            memcpy(g,t,sizeof g);
            int cnt=0;
            for(int k=0;k<5;k++)
                if(i>>k&1) {
                    touch(1,k+1);
                    cnt++;
                }
            for(int j=2;j<=5;j++)
                for(int k=1;k<=5;k++)
                {
                    if(g[j-1][k]=='0') {
                        touch(j,k);
                        cnt++;
                    }
                }
            bool flag=true;
            for(int j=1;j<=5;j++)
                if(g[5][j]=='0') flag=false;
            if(flag) res=min(res,cnt);
        }
        if(res>6) puts("-1");
        else cout<<res<<endl;
    }


    return 0;
}

我们通过题目可以发现,想要使上面一层的灯必须要按下面的开关,唯独第一行不同,我们用指数型枚举,暴力枚举一下第一行,如果最终,最后一行全部被关上则方案可行,否则方案不可行,记录每一次开关灯的次数即可完成此题。

奇怪的汉诺塔

汉诺塔问题,条件如下:

1、这里有A、B、C和D四座塔。

2、这里有n个圆盘,n的数量是恒定的。

3、每个圆盘的尺寸都不相同。

4、所有的圆盘在开始时都堆叠在塔A上,且圆盘尺寸从塔顶到塔底逐渐增大。

5、我们需要将所有的圆盘都从塔A转移到塔D上。

6、每次可以移动一个圆盘,当塔为空塔或者塔顶圆盘尺寸大于被移动圆盘时,可将圆盘移至这座塔上。

请你求出将所有圆盘从塔A移动到塔D,所需的最小移动次数是多少。

输入格式
没有输入

输出格式
对于每一个整数n(1≤n≤12),输出一个满足条件的最小移动次数,每个结果占一行。

输入样例:
没有输入
输出样例:
参考输出格式

我们将三柱转化为四柱,四柱就是枚举一下将1-n-1个盘子放在辅助柱上,转化为三柱去做,然后看能不能优化,非常简单,来看代码。

#include<iostream>
#include<cstring>
#include<algorithm>

using namespace std;

int f[13],g[13];

int main(){
    //freopen("data.in","r",stdin);
    //freopen("data.out","w",stdout);
    for(int i=1;i<=12;i++) f[i]=(1<<i)-1;
    memset(g,0x3f,sizeof g);
    g[1]=1;
    g[0]=0;
    cout<<g[1]<<endl;
    for(int i=2;i<=12;i++)
    {
        for(int j=0;j<i;j++)
        {
            g[i]=min(g[i],g[j]+f[i-j]+g[j]);
        }
        cout<<g[i]<<endl;
    }

    return 0;
}

约数之和

假设现在有两个自然数A和B,S是AB的所有约数之和。

请你求出S mod 9901的值是多少。

输入格式
在一行中输入用空格隔开的两个整数A和B。

输出格式
输出一个整数,代表S mod 9901的值。

数据范围
0≤A,B≤5×107
输入样例:
2 3
输出样例:
15
注意: A和B不会同时为0。

我们知道约数之和公式为 ∑ ( 2 n 1 ) ∗ ∑ ( 3 n 2 ) ∗ ∑ ( 5 n 3 ) ⋯ \sum(2^{n1})*\sum(3^{n2})*\sum(5^{n3})\cdots (2n1)(3n2)(5n3)
每一项都是拆开质因数后的等比级数。我们可以用等比数列求和公式,但是不适用于这题,因为取模运算只对加减乘有交换律,我们不希望出现除号,当然我们也不可能一个个加过去,必然会超时。那怎么办呢?这题实际上是要求我们,模拟一下等比数列求和公式的推导流程。
1 + q 2 + q 3 + ⋯ + q n 1+q^2+q^3+\cdots+q^n 1+q2+q3++qn
当n时奇数时:
( 1 + q 2 + q 3 + ⋯ + q n / 2 ) + ( q n / 2 + 1 + q n / 2 + 2 + q n / 2 + 3 + ⋯ + q n ) (1+q^2+q^3+\cdots+q^{n/2}) + (q^{n/2+1}+q^{n/2+2}+q^{n/2+3}+\cdots+q^n) (1+q2+q3++qn/2)+(qn/2+1+qn/2+2+qn/2+3++qn)
我们继续对上式化简:
( 1 + q 2 + q 3 + ⋯ + q n / 2 ) + q n / 2 ∗ ( 1 + q 2 + q 3 + ⋯ + q n / 2 ) (1+q^2+q^3+\cdots+q^{n/2}) + q^n/2*(1+q^2+q^3+\cdots+q^{n/2}) (1+q2+q3++qn/2)+qn/21+q2+q3++qn/2
再化简
( q n / 2 + 1 ) ( 1 + q 2 + q 3 + ⋯ + q n / 2 ) (q^n/2+1) (1+q^2+q^3+\cdots+q^{n/2}) (qn/2+1)(1+q2+q3++qn/2)
我们将范围放缩了一般
当n是偶数时:
我们把他变为奇数就可以了。
我们来看代码:

#include<iostream>
#include<algorithm>
#include<cstring>

using namespace std;

const int mod=9901;

int qui(int a,int b)
{
    int res=1;
    a=a%mod;
    while(b)
    {
        if(b&1) res=(res*a)%mod;
        b>>=1;
        a=(a*a)%mod;
    }
    return res;
}

int sum(int a,int b)
{
    if(b==0) return 1;
    if(b&1) return ((qui(a,(b+1)/2)+1)*sum(a,(b-1)/2))%mod; 
    return (a%mod*sum(a,b-1)+1)%mod;
}

int main(){
    //freopen("data.in","r",stdin);
    //freopen("data.out","w",stdout);
    int a,b;
    cin>>a>>b;
    if(a==0)
        cout<<0<<endl;
    else
    {
        int ans=1;
        for(int i=2;a!=1;i++)
        {
            int cnt=0;
            while(a%i==0)
            {
                a/=i;
                cnt++;
            }
            if(cnt) ans=(ans*sum(i,cnt*b))%mod; 
        }    
        cout<<ans<<endl;
    }

    return 0;
}

分形之城

城市的规划在城市建设中是个大问题。

不幸的是,很多城市在开始建设的时候并没有很好的规划,城市规模扩大之后规划不合理的问题就开始显现。

而这座名为 Fractal 的城市设想了这样的一个规划方案,如下图所示:

当城区规模扩大之后,Fractal 的解决方案是把和原来城区结构一样的区域按照图中的方式建设在城市周围,提升城市的等级。

对于任意等级的城市,我们把正方形街区从左上角开始按照道路标号。

虽然这个方案很烂,Fractal 规划部门的人员还是想知道,如果城市发展到了等级 N,编号为 A 和 B 的两个街区的直线距离是多少。

街区的距离指的是街区的中心点之间的距离,每个街区都是边长为 10 米的正方形。

输入格式
第一行输入正整数n,表示测试数据的数目。

以下n行,输入n组测试数据,每组一行。

每组数据包括三个整数 N,A,B, 表示城市等级以及两个街区的编号,整数之间用空格隔开。

输出格式
一共输出n行数据,每行对应一组测试数据的输出结果,结果四舍五入到整数。

数据范围
1≤N≤31,
1≤A,B≤22N,
1≤n≤1000
输入样例:
3
1 1 2
2 16 1
3 4 33
输出样例:
10
30
50

#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>

#define ll long long
#define pll pair<ll,ll>

using namespace std;

pll work(ll n,ll k){
    if(n==0) return {0,0};
    ll p=1ll<<(2*n-2);
    ll d=1ll<<(n-1);  
    pll temp=work(n-1,k%p);
    ll res=k/p;
    if(res==0) return {temp.second,temp.first};
    else if(res==1) return {temp.first,temp.second+d};
    else if(res==2) return {temp.first+d,temp.second+d};
    else return {2*d-temp.second-1,d-1-temp.first};

}


int main(){
    //freopen("data.in","r",stdin);
    //freopen("data.out","w",stdout);
    int T;
    cin>>T;
    while(T--)
    {
        ll n,a,b;
        cin>>n>>a>>b;
        pll t1=work(n,a-1);
        pll t2=work(n,b-1);
        double d1=t1.first-t2.first;
        double d2=t1.second-t2.second;
        double res=sqrt(d1*d1+d2*d2)*10;
        printf("%.0lf\n",res);
    }


    return 0;
}

这题的难度非常大,我来给大家拆解一下,递归找点,work(n,k),n带表第n个城市,k代表城市编号。我们的递归思路就是,找到上一级演化前的城市。然后对演化前的城市进行旋转运算 c o s t − s i n t s i n t c o s t \begin{matrix} cost & -sint \\ sint & cost \end{matrix} costsintsintcost
找到上一级城市后,对其进行运算旋转运算,第一块旋转90,这个比较简单,(x,y)变成(-y,x)做一个边界处理之后就是(y,x)
第二块和第三块比较简单,不讲了
第四块转化为(y,-x) 做一个边界处理(2d-y-1,d-x-1)
然后得到坐标后,把距离算出来,大功告成。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值