基本算法之递推与递归的简单应用

常见的枚举形式

枚举形式一般的遍历方法
多项式循环,递推
指数递归,位运算
排列递归 ,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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

落春只在无意间

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

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

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

打赏作者

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

抵扣说明:

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

余额充值