蓝桥杯C++大学B组一个月冲刺记录2024/3/13

蓝桥杯C++大学B组一个月冲刺记录2024/3/13

规则:每日三题

向日葵的花语是说不出的爱恋
不过今天有点水题了

1.有序分数

给定一个整数 N,请你求出所有分母小于或等于 N,大小在 [0,1] 范围内的最简分数,并按从小到大顺序依次输出。

这个题在被划分为递归一章,是由于Stern-Brocot Tree可以通过中序递归来解决这个问题
但是这个题数据范围仅n <160。所以时间复杂度为O(n2logn)的暴力做法也可以解决这个题

#include<iostream>
#include<vector>
#include<algorithm>

using namespace std;

typedef pair<int,int>PII;

vector<PII>q;

int n;

bool cmp(PII a,PII b){
    return a.first * b.second < a.second * b.first;
}

int gcd(int a,int b){
    return b?gcd(b,a % b):a;
}

int main(){
    
    cin >> n;

    q.push_back({0,1});

    for(int i = 1;i <= n; ++i){
        for(int j = 1;j <= i;++j){
            if(gcd(i,j) == 1) q.push_back({j,i});
        }     
    }

    sort(q.begin(),q.end(),cmp);

    for(int i = 0;i < q.size();++i){
        cout << q[i].first << '/' << q[i].second << endl;
    }
    return 0;    
}

以下是y总的递归做法,时间复杂度O(n2)

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

using namespace std;

int n;

void dfs(int a, int b, int c, int d)
{
    if (a + c > n) return;

    dfs(a, b, a + c, b + d);
    printf("%d/%d\n", b + d, a + c);
    dfs(a + c, b + d, c, d);
}

int main()
{
    scanf("%d", &n);
    puts("0/1");
    dfs(1, 0, 1, 1);
    puts("1/1");

    return 0;
}

2.递归实现指数型枚举

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

简单的dfs,比较巧妙的是用二进制编码来记录状态

#include<iostream>

using namespace std;

const int M = 20;

int p[M];
int n;

void dfs(int i){
    if(i > n){
        for(int i = 1;i <= n; ++i){
            if(p[i] == 1) cout << i << ' ';
            else continue;   
        }
        cout << '\n';
    }

    else{
        p[i] = 1;
        dfs(i + 1);
        p[i] = 0;
        dfs(i + 1);
    }

}

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

3.带分数

100 可以表示为带分数的形式:100=3+69258/714 还可以表示为:100=82+3546/197
注意特征:带分数中,数字 1∼9分别出现且只出现一次(不包含 0)。
类似这样的带分数,100有 11 种表示法。

按道理说应该是递推+剪枝
但是数据范围确实小,纯暴力7032ms过掉了

#include<iostream>

using namespace std;

int n;
bool st[10];
int ans = 0;

bool juge(int x,int l,int r){
    int p[20],cnt = 9;
    while(cnt >= 1){
        p[cnt] = x%10;
        cnt--;
        x = x/10;
    }

    int a = 0,b = 0,c = 0;
    for(int i = 1;i <= l;++i) a = a * 10 + p[i];
    for(int i = l + 1;i <= r; ++i) b = b * 10 + p[i];
    for(int i = r + 1;i <= 9; ++i) c = c * 10 + p[i];

    return a+ b/c == n && b%c ==0;
}

int check(int x){
    int tot = 0;
    for(int i = 1;i < 8;++i){
        for(int j = i+1;j < 9;++j){
            if(juge(x,i,j) == true) tot ++;
        }
    }

    return tot;
}
void dfs(int pos,int x){
    if(pos > 9) ans += check(x);
    else{
        for(int i = 1;i <= 9; ++i){
            if(!st[i]){
                st[i] = true;
                dfs(pos+1,x*10+i); 
                st[i] = false;  
            }
        }
    }
}

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

    cout << ans << endl;

    return 0;

        
}

为了惩罚昨天的水题行为:对这个题进行优化:
暴力优化 + 剪枝(时间耗时:2202ms只有上面的那个三分之一的耗时)
(1)将数字各位数字直接存入在数组里方便judge函数处理(减去了整型放入数组的时间复杂度)
(2)当a >= n时候由于 b / c 不小于0可以直接return false
(3)其实可以看到,在列举l与r以及判断的操作,其实是符合二分的。但是check穷举l,r只有28种情况,最多被二分优化常数为5

#include<iostream>

using namespace std;

int pos[20];
bool st[20];
int ans = 0,n;

bool judge(int l,int r){
    int a = 0,b = 0,c = 0;
    for(int i = 1;i <= l; ++i){
        a = a * 10 + pos[i];
        if(a >= n) return false;
    }
    for(int i = l + 1;i <= r;++i) b = b * 10 + pos[i];
    for(int i = r + 1;i <= 9;++i) c = c * 10 + pos[i];


    return a + b / c == n&& b % c == 0;  
}


int check(){
    int tot = 0;
    for(int i = 1;i < 8;++i){
        for(int j = i + 1;j < 9;++j){
            if(judge(i,j)) tot ++;
        }
    }

    return tot;

}


void dfs(int k){
    if(k > 9) {
        ans += check();
        return;
    }

    else{
        for(int i = 1;i <= 9;++i){
            if(!st[i]){
                pos[k] = i;
                st[i] = true;
                dfs(k + 1);
                st[i] = false;
            }
        }
    }
}

int main(){
    cin >> n;
    dfs(1);
    cout << ans << endl;

    return 0;
}

数学优化
当n == a + b/c 可以等价于 n * c - a * c = b
所以发现就只用列举c和a,判断b是否符合规定即可
(耗时1861ms)

#include<iostream>

using namespace std;

const int M = 15;
bool st[M];

typedef long long LL;
int n,ans = 0;
bool check(int a,int c){
    
    int b = (LL)n * c - (LL)a * c;
    
    if(!a||!b||!c) return false;

    bool cpy[20];
    for(int i = 1;i <= 9;++i) cpy[i] = st[i];//对st备份

    while(b != 0){
        int t = b % 10;
        b = b/10;
        if(cpy[t]) return false;
        else cpy[t] = true;
    }

    for(int i = 1;i <= 9; ++i){
        if(cpy[i] == false) return false;
    }
    return true;
}

void dfs_c(int a,int c){

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

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

int main(){
    cin >> n;

    dfs_a(0);

    cout << ans << endl;

    return 0;
}
  • 8
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值