算法刷题day03

目录

  • 引言
  • 一、递归实现组合型枚举
  • 二、带分数
  • 三、飞行员兄弟
  • 四、翻硬币
  • 五、总结

引言

这篇也是在题目是在写博客之前刷的,然后补一下,这是补的最后一次了,以后就一天写一篇就够了。

一、递归实现组合型枚举

标签:DFS

题目描述:

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

using namespace std;

const int N = 30;

int n, m;
int a[N];
bool st[N];
int ways[N];

void dfs(int u, int s)  //[1,m]
{
    if(m - u > n - s + 1) return;
    if(u == m)
    {
        for(int i = 0; i < m; ++i)
        {
            printf("%d ", ways[i]);
        }
        puts("");
    }
    
    for(int i = s; i <= n; ++i)
    {
        if(st[i]) continue;
        
        st[i] = true;
        ways[u] = i;
        dfs(u+1, i+1);
        
        st[i] = false;
    }
}

int main()
{
    scanf("%d%d", &n, &m);
    
    dfs(0, 1);
    
    return 0;
}

二、带分数

标签:dfs

题目描述:

100 可以表示为带分数的形式:100=3+69258714还可以表示为:100=82+3546197注意特征:带分数中,数字 19 分别出现且只出现一次(不包含 0)。

类似这样的带分数,10011 种表示法。

输入格式
一个正整数。

输出格式
输出输入数字用数码 19 不重复不遗漏地组成带分数表示的全部种数。

数据范围
1≤N<106
输入样例1100
输出样例111
输入样例2105
输出样例26

示例代码:

#include 
#include 
#include 

using namespace std;

typedef long long LL;

const int N = 20;

int n;
bool st[N], backup[N];
int ans;

bool check(int a, int c)
{
    LL b = (LL)n * c - a * c;
    
    if(!a || !c || !b) return false;
    
    memcpy(backup, st, sizeof st);
    while(b)
    {
        int t = b % 10;
        if(!t || backup[t]) return false;
        backup[t] = true;
        b /= 10;
    }
    
    for(int i = 1; i <= 9; ++i)
    {
        if(!backup[i]) return false;
    }
    
    return true;
}

void dfs_c(int u, int a, int c)
{
    if(u == 8) return;
    
    if(check(a,c)) ans++;
    
    for(int i = 1; i <= 9; ++i)
    {
        if(!st[i])
        {
            st[i] = true;
            dfs_c(u+1,a,c*10+i);
            st[i] = false;
        }
    }
}

void dfs_a(int u, int a)
{
    if(a >= n) return;
    if(a) dfs_c(u,a,0);
    
    for(int i = 1; i <= 9; ++i)
    {
        if(!st[i])
        {
            st[i] = true;
            dfs_a(u+1, a*10+i);
            st[i] = false;
        }
    }
}

int main()
{
    cin >> n;
    
    dfs_a(0, 0);
    
    cout << ans << endl;
    
    return 0;
}

第二种写法

#include 
#include 
#include 
#include 
#include 

using namespace std;

string str = "123456789";

int get(int l, int r)
{
	int res = 0;
	for(int i = l; i <= r; ++i)
	{
		res = res * 10 + str[i] - '0';	
	}
	
	return res;
}

int main()
{
    int n;
    cin >> n;
    
    int ans = 0;
    do
    {
        for(int i = 0; i < str.size(); ++i)
        {
            for(int j = i + 1; j < str.size(); ++j)
            {
                int a = get(0,i);
                int b = get(i+1,j);
                int c = get(j+1,8);
                
                if(!a || !b || !c) continue;
                if(n*c -a*c == b) ans++;
            }
        }
    }while(next_permutation(str.begin(),str.end()));
    
    cout << ans << endl;
    
    return 0;
}

三、飞行员兄弟

标签:DFS

题目描述:

“飞行员兄弟”这个游戏,需要玩家顺利的打开一个拥有 16 个把手的冰箱。

已知每个把手可以处于以下两种状态之一:打开或关闭。

只有当所有把手都打开时,冰箱才会打开。

把手可以表示为一个 4×4 的矩阵,您可以改变任何一个位置 [i,j]上把手的状态。

但是,这也会使得第 i 行和第 j 列上的所有把手的状态也随着改变。

请你求出打开冰箱所需的切换把手的次数最小值是多少。

输入格式
输入一共包含四行,每行包含四个把手的初始状态。
符号 + 表示把手处于闭合状态,而符号 - 表示把手处于打开状态。
至少一个手柄的初始状态是关闭的。

输出格式
第一行输出一个整数 N,表示所需的最小切换把手次数。

接下来 N 行描述切换顺序,每行输出两个整数,代表被切换状态的把手的行号和列号,数字之间用空格隔开。

注意:如果存在多种打开冰箱的方式,则按照优先级整体从上到下,同行从左到右打开。

数据范围
1≤i,j≤4
输入样例:
-+--
----
----
-+--
输出样例:
6
1 1
1 3
1 4
4 1
4 3
4 4

示例代码:

#include 
#include 
#include 
#include 

using namespace std;

typedef pair<int,int> PII;

const int N = 5;

char g[N][N], backup[N][N];

void turn(int x, int y)
{
    for(int i = 0; i < 4; ++i) 
    {
        if(g[i][y] == '-') g[i][y] = '+';
        else g[i][y] = '-';
    }
    
    for(int i = 0; i < 4; ++i) 
    {
        if(g[x][i] == '-') g[x][i] = '+';
        else g[x][i] = '-';
    }
    
    if(g[x][y] == '-') g[x][y] = '+';
    else g[x][y] = '-';
}

bool check()
{
    for(int i = 0; i < 4; ++i)
        for(int j = 0; j < 4; ++j)
            if(g[i][j] == '+') 
                return false;
                
    return true;
}

int main()
{
    for(int i = 0; i < 4; ++i) scanf("%s", g[i]);
    
    vector<PII> res(20);
    for(int op = 0; op < 1 << 16; op++)
    {
        vector<PII> step;
        memcpy(backup, g, sizeof g);
        for(int k = 0; k < 16; ++k)
        {
            if(op >> k & 1)
            {
                int x = k / 4;
                int y = k % 4;
                step.push_back({x,y});
                turn(x,y);
            }
        }
        
        if(check() && res.size() > step.size()) res = step;
        
        memcpy(g, backup, sizeof g);
    }
    
    printf("%d\n", res.size());
    for(auto x: res) printf("%d %d\n", x.first+1, x.second+1);
    
    return 0;
}

四、翻硬币

标签:递推

题目描述:

小明正在玩一个“翻硬币”的游戏。

桌上放着排成一排的若干硬币。我们用 * 表示正面,用 o 表示反面(是小写字母,不是零)。

比如,可能情形是:**oo***oooo

如果同时翻转左边的两个硬币,则变为:oooo***oooo

现在小明的问题是:如果已知了初始状态和要达到的目标状态,每次只能同时翻转相邻的两个硬币,那么对特定的局面,最少要翻动多少次呢?

我们约定:把翻动相邻的两个硬币叫做一步操作。

输入格式
两行等长的字符串,分别表示初始状态和要达到的目标状态。

输出格式
一个整数,表示最小操作步数

数据范围
输入字符串的长度均不超过100。数据保证答案一定有解。

输入样例1:
**********
o****o****
输出样例1:
5
输入样例2:
*o**o***o***
*o***o**o***
输出样例2:
1

示例代码:

#include 
#include 
#include 

using namespace std;

const int N = 110;

string src, dest;

void turn(int x)
{
    if(src[x] == 'o') src[x] = '*';
    else src[x] = 'o';
}

int main()
{
    cin >> src >> dest;
    
    int cnt = 0;
    for(int i = 0; i < src.size() - 1; ++i)
    {
        if(src[i] != dest[i])
        {
            cnt++;
            turn(i);
            turn(i+1);
        }
    }
    
    printf("%d\n", cnt);
    return 0;
}

五、总结

  • soti和substr会很耗时的,尽量不要再循环中写
  • scanf %s二维字符数组g[i][j]时,一般都是g[i],&g[i]也行不过会警告的
  • 看清题目要求,一些细节等东西一定要了解清楚

你可能感兴趣的:(#,算法刷题,算法,深度优先,图论)