蓝桥杯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;
}