《算法竞赛进阶指南》0.2递推与递归

92. 递归实现指数型枚举

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

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

输出格式
每行输出一种方案。
同一行内的数必须升序排列,相邻两个数用恰好1个空格隔开。
对于没有选任何数的方案,输出空行。
本题有自定义校验器(SPJ),各行(不同方案)之间的顺序任意。

数据范围
1≤n≤15

输入样例
3

输出样例:

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

#include <iostream>

using namespace std;

int 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); //选,整数二进制表示下的第u位赋值1
}

int main()
{
    cin >> n;
    dfs(0,0);
    return 0;
}

93. 递归实现组合型枚举

从 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>

using namespace std;

int n, m;

void dfs(int u, int sum, int state)
{
    if(sum + n - u < m) return;//“剪枝”,选上剩余的也不够,直接返回
    if(sum == m)
    {
        for(int i = 0; i < n; i++)
            if(state >> i & 1)
                cout << i + 1 << " ";
        cout << endl;
        return ;
    }
    
    if(u == n) return;//枚举完了n个,还没有找到,说明当前方案不合法,直接返回
    
    dfs(u + 1, sum + 1, state | 1 << u);//选
    dfs(u + 1, sum, state);//不选
}

int main()
{
    cin >> n >> m;
    dfs(0, 0, 0);
    return 0;
}

非递归解法

#include <iostream>
#include <stack>

using namespace std;

int n, m;

//非递归
struct State
{
    int pos, u, sum, state;
};

void dfs(int u, int sum, int state)
{
    // 0:
    if(sum + n - u < m) return;//“剪枝”,选上剩余的也不够,直接返回
    if(sum == m)
    {
        for(int i = 0; i < n; i++)
            if(state >> i & 1)
                cout << i + 1 << " ";
        cout << endl;
        return ;
    }
    if(u == n) return;//枚举完了n个,还没有找到,说明当前方案不合法,直接返回
    
    dfs(u + 1, sum + 1, state | 1 << u);//选
    // 1 :
    
    dfs(u + 1, sum, state);//不选
    // 2:
}

int main()
{
    cin >> n >> m;
    //dfs(0, 0, 0);
    
    stack<State> stk;
    stk.push({0, 0, 0, 0}); //大括号
    
    while(stk.size())
    {
        auto t = stk.top(); // 拿出栈顶元素
        stk.pop();
        
        if(t.pos == 0)
        {
            if(t.sum + n - t.u < m) continue;
            if(t.sum == m)
            {
                for(int i = 0; i < n; i++)
                    if(t.state >> i & 1)
                        cout << i + 1 << " ";
                cout << endl;
                continue ;
            }
            
            t.pos = 1;
            stk.push(t);
            stk.push({0, t.u + 1, t.sum + 1, t.state | 1 << t.u});
            
        }
        else if(t.pos == 1)
        {
            t.pos = 2;
            stk.push(t);
            stk.push({0, t.u + 1, t.sum, t.state});
            
        }
        else continue;
    }
    
    return 0;
}

94. 递归实现排列型枚举

把 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 <vector>

using namespace std;

int n, m;
vector<int> path;

void dfs(int u, int state)
{
    if(u == n)
    {
        for(auto x : path) cout << x << " ";
        cout << endl;
        return;
    }
    for(int i = 0; i < n; i++)
        if(!(state >> i & 1))
        {
            path.push_back(i + 1);
            dfs(u + 1, state | 1 << i);
            path.pop_back();
        }
}

int main()
{
    cin >> n >> m;
    dfs(0, 0);
    return 0;
}

95. 费解的开关

你玩过“拉灯”游戏吗?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>

using namespace std;

const int INF = 1000000;

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

void turn(int x, int y)
{
    for(int i = 0; i < 5; i++ )
    {
        int a = x + dx[i], b = y + dy[i];
        if(a >= 0 && a < 5 && b >= 0 && b < 5)
        {
            g[a][b] ^= 1;//灯的状态取反,通常使用异或操作符
        }
    }
    
}

int work()
{
    int ans = INF;
    for(int k = 0; k < 1 << 5; k++)
    {
        int res = 0;//注意初始化为0啊!!!
        char backup[10][10];
        memcpy(backup, g, sizeof g);//还原现场,备份第一行,还原第一行状态
        for(int j = 0; j < 5; j++)
            if( k >> j & 1)
            {
                res ++;
                turn(0, j);
            }
        
        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 is_successful = true;
        for(int j = 0; j < 5; j++)
            if(g[4][j] == '0')
            {
                is_successful = false;
                break;
            }
            
        if(is_successful) ans = min(ans,res);//还原现场,备份第一行,还原第一行状态
        
        memcpy(g, backup, sizeof g);
    }
    if(ans > 6) return -1;
    return ans;
}

int main()
{
    int T;
    cin >> T;
    while(T--)
    {
        for(int i = 0; i < 5; i++) cin >> g[i];
        cout << work() << endl;
    }
    return 0;
}

96. 奇怪的汉诺塔

汉诺塔问题,条件如下:

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

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

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

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

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

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

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

输入格式
没有输入

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

输入样例:
没有输入

输出样例:
参考输出格式

#include <iostream>
#include <cstring>

using namespace std;

int main()
{
    int d[15], f[15];
    
    d[1] = 1;
    for(int i = 2; i <=12; i++)
    {
        d[i] = d[i - 1] * 2 + 1;
    }
    
    memset(f, 0x3f, sizeof f); //初始化无穷大
    f[0] = 0;
    for(int i = 1; i <= 12; i++)
        for(int j = 0; j < i; j++) //把前j个盘子在4塔的模式下移动
            f[i] = min(f[i], f[j] * 2 + d[i - j]);
            
    for(int i = 1; i <= 12; i++) cout<<f[i]<<endl;        
        
    return 0;
}

97. 约数之和

假设现在有两个自然数A和B,S是A^B的所有约数之和。
请你求出S mod 9901的值是多少。

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

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

数据范围
0≤A,B≤5×107

输入样例
2 3

输出样例
15

注意: A和B不会同时为0。

#include <iostream>

using namespace std;

const int mod = 9901;

int qmi(int a, int k)//快速幂
{
    a %= mod;
    int res = 1;
    while(k)
    {
        if(k & 1) res = res * a % mod;
        a = a * a % mod;
        k >>= 1;
    }
    return res;
}

int sum(int p, int k)
{
    if(k == 0) return 1;//可省
    if(k % 2 == 0) return (p % mod * sum(p, k - 1) + 1) % mod; //1 为p^0,注意这里k%2为真是偶数 k%2 == 0,即k%2为假,是奇数
    return (1 + qmi(p, k/2 + 1)) * sum(p, k/2) % mod; //偶数
    // sum(p,k) = (1 + p^(k/2 + 1)) * sum(p, k/2);
}

int main()
{
    int A, B;
    cin >> A >> B;
    int res = 1;
    for(int i = 2; i <= A; i++)//求A有多少个因子i,分解质因数
    {
        int s = 0; //因子个数
        while(A % i == 0)
        {
            s ++;
            A /= i;
        }
        if(s)//因子个数大于1
        {
            res = res * sum(i, s * B) % mod;
        }
    }
    if(!A) res = 0;//A 为 0
    cout << res << endl;
    return 0;
}

98. 分形之城

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

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

而这座名为 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 <cmath>

using namespace std;

typedef long long LL;
typedef pair<LL, LL> PLL;

PLL calc(LL n, LL m)
{
    if(n == 0) return {0, 0};
    LL len = 1ll << n - 1, cnt = 1ll << (2 * n - 2); //这里为1ll,n=32, 2*n整型溢出, 第一个可以不用改
    auto pos = calc(n - 1, m % cnt); //子问题结果
    auto x = pos.first, y = pos.second;
    auto z = m / cnt; // 确定哪一个位置
    if(z == 0) return {y, x}; //左上
    if(z == 1) return {x, y + len}; //右上
    if(z == 2) return {x + len, y + len}; //右下
    if(z == 3) return {-y + 2 * len - 1, -x + len - 1};//左下, 大括号,返回坐标
}

int main()
{
    int T;
    cin >> T;
    while(T --)
    {
        LL N, A, B;
        cin >> N >> A >> B;
        auto ac = calc(N, A - 1);
        auto bc = calc(N, B - 1);
        double x = ac.first - bc.first, y = ac.second - bc.second;
        printf("%.0lf\n", sqrt(x * x + y * y) * 10); //结果四舍五入到整数
    }
    return 0;
}



posted @ 2019-05-09 08:27  WMXNLFD  阅读(271)  评论(0编辑  收藏  举报