2022年第十三届蓝桥杯大赛软件类决赛C/C++大学C组真题

打卡第6篇。

一套对于我来说抽象程度难以形容的题。。。。。。从第一题开始就步履维艰,第二题要调很久,第三题不难,第四题很抽象,第五题巧妙化简之后才好点,不然必爆最多5分,最后两题我不多说什么了。。。。。。还是自己太菜了有待提高。。。。

C.取模

打个表看看怎么个事。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int main(){
	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
	ll t;cin>>t;
	while(t--){
		ll n,m;cin>>n>>m;
		bool flag=0;
		for(ll i=1;i<m-1;i++){
			for(ll j=i+1;j<=m;j++){
				if((n%i)==(n%j)){
					cout<<n<<"%"<<i<<" = "<<(n%i)<<" "<<n<<"%"<<j<<" = "<<(n%j)<<'\n';
				}
			}
		}
	}
	return 0;
}

  

 

 

发现几种规律:

1. n==m时,必定有解,因为y可以等于m,式子化简为n%x==n%n,任何数模1都等于0,所以n%1==n%n,对于任意的n恒成立。

 2. n<m时,除了1,2没有解,其他m-n>=2的情况下都可以有解。

3.  n>m时,就不一定了。

暴力解(可以AC)

​
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int main(){
	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
	ll t;cin>>t;
	while(t--){
		ll n,m;cin>>n>>m;
		if(n==m)cout<<"Yes"<<'\n';
		else{
			ll flag=0;
			for(ll y=2;y<=m;y++){
				for(ll x=1;x<y;x++){
					if(n%y==n%x){
						flag=1;
						break;
					}
				}
				if(flag==1){
					cout<<"Yes"<<'\n';
					break;
				}
			}
			if(flag==0)cout<<"No"<<'\n';
		}
	}
	return 0;
}

​

 换了个写法错了,洛谷也没过,让我看看怎么个事。。

哥们帮我打了个表。

#include <bits/stdc++.h>
using namespace std;
#define ll long long

int main()
{
    int m = 100000000;
    for (int n = m; n >= 1; n--)
    {
        ll flag = 0;
        for (ll x = 1; x <= m - 1; x++)
        {
            for (ll y = x + 1; y <= m; y++)
            {
                if (n % y == n % x)
                {
                    if (x != 1)
                    {
                        cout << n << ' ';
                        cout << x << ' ' << y << endl;
                        system("pause");
                    }
                    flag = 1;
                    break;
                }
            }
            if (flag == 1)
            {
                // cout << "Yes" << '\n';
                break;
            }
        }
        // if (flag == 0)
        //  cout << "No" << '\n';
    }
}

 

6.。。。。所以y放内层很可能就超时了,不放心还可以再试试两个代码同时运行这个样例要用多少次,就一目了然了。

中国剩余定理,了解一下。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

int main() {
    ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
    ll t; cin >> t;
    while (t--) {
        ll n, m; cin >> n >> m;
        if (m > 23) {  // 对于 m 大于 23 的情况,可以直接输出 "Yes"
            cout << "Yes" << '\n';
        } else {
            bool flag = false;
            // 对于 m 小于等于 23,遍历所有可能的 (x, y) 组合
            for (ll x = 1; x < m; x++) {
                for (ll y = x + 1; y <= m; y++) {
                    if (n % x == n % y) {
                        flag = true;
                        break;
                    }
                }
                if (flag) break;
            }
            if (flag) cout << "Yes" << '\n';
            else cout << "No" << '\n';
        }
    }
    return 0;
}

D.内存空间

这个题不难,细节和坑比较多,注意读题和输入输出格式。。。 

#include <bits/stdc++.h>
#define int long long//开个long long保险一点
using namespace std;
int t , ans , a[4];
string s , mp[4] = {"GB" , "MB" , "KB" , "B"};
//输入字符串和内存字符串
void solve1(){//整理数组内存
	int m , num;//m为一个变量的内存,num为数组的大小
	if(s[0] == 'i') m = 4;
	else m = 8;
	for(int i = 0; i < s.size(); i++){
		if(s[i] == '['){//找中括号里面的数
			num = 0;
			for(int j = i + 1; s[j] != ']'; j++)
				num = num * 10 + s[j] - '0';
			ans += num * m;
		}
	}
	return;
}
void solve2(){//整理变量内存
	int m;//m为一个变量的内存
	if(s[0] == 'i') m = 4;
	else m = 8;
	for(int i = 0; i < s.size(); i++)
		if(s[i] == '=') ans += m;//找等于号
	return;
}
void solve3(){//整理字符串内存
	for(int i = 0; i < s.size(); i++){
		if(s[i] == '"' && s[i - 1] == '='){
        //有可能出现引号,但是前面没有等于号,不算一个字符串的开始
			for(int j = i + 1; s[j] != '"'; j++)
				ans++;//字符串长度
		}
	}
	return;
}
signed main(){
	cin >> t;
	getchar();//输入t后再缓存区内会有换行符,所以要先getchar一下,否则会出问题
	while(t--){
		getline(cin , s);
		if(s[0] == 'i' || s[0] == 'l'){//若为int long类型的
			if((s[0] == 'i' && s[3] == ' ') || (s[0] == 'l' && s[4] == ' '))//若为变量
				solve2();
			else solve1();//若为数组
		}
		else solve3();//若为字符串
	}
	int cur = 4;
	while(ans){
		a[--cur] = ans % 1024;
		ans /= 1024;
	}//整理输出
	for(int i = 0; i < 4; i++){
		if(a[i]) cout << a[i] << mp[i];
	}//输出
	return 0;
}

E.斐波那契数组

暴力非全过代码 

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N=1e6+2;
ll st[N];
void init(ll st[]){
	st[0]=st[1]=1;
	for(ll i=2;i<N;i++){
		st[i]=st[i-1]+st[i-2];
	}
}  
int main(){
	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
	ll n;cin>>n;
	vector<ll> a(n);
	ll count=0;

	init(st);
	for(ll i=0;i<n;i++){
		cin>>a[i];
		if(st[i]!=a[i])count++;
	}
	cout<<count;
	return 0;
}

注意,a0=a1,并非a0=a1=1,也有可能是2,2,4,6,10,16,…,

正确思路:

首先我们来证明一个东西:

如果 a 是一个斐波那契数组,那么满足以下关系式:

ai​ = fi​×a1​(i≥3)

其中,f 表示原始的斐波那契数列(1,1,2,3,5,8.......)。

证明如下:

设 a1​=a2​=x,则 a3​=2x,a4​=(1+2)x=3x,a5​=(2+3)x=5x ······,将每个系数记到 b 里:b1​=b2​=1,bi​=bi−1​+bi−2​(i≥3),与 f 的递推式是相同的。

注意到数据范围,1≤ai​≤1e6,也就是说题目中的有效的斐波那契数组至多也是 30 项,我们可以输入一个很大的n,但由于ai的数据限制,所以我们可以先把斐波那契数列预处理出来,统计答案也只需要 30 个数,最后输出把后面的数加上就行了。

因为只需要满足 a0​=a1​ 这个条件,所以我们斐波那契数组是可以在一般的斐波那契数列上乘倍数,最坏情况下是需要乘 1e6 的。

因为之前预处理了斐波那契数列,所以当前的数列与乘完倍数的斐波那契数组不同的个数的最小值,便是答案。

OK啊,问题开始变得有趣起来,让我们开始手搓代码。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N=1e6;
int main(){
	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
	ll n;cin>>n;
	ll a[n+1];
	for(ll i=1;i<=n;i++)cin>>a[i];
	
	ll st[31];
	st[0]=st[1]=st[2]=1;
	for(ll i=3;i<=30;i++)st[i]=st[i-1]+st[i-2];
	
	//for(ll i=1;i<=30;i++)cout<<st[i]<<" ";
	ll ans=LONG_MAX;
	for(ll i=1;i<=N;i++){
		ll res=0;
		for(ll j=1;j<=min(30ll,n);j++){
			res+=(a[j]!=i*st[j]);
		}
		ans=min(ans,res);
	}
	cout<<max(ans+(n-30),ans);
	return 0;
}

F.近似 GCD

 

注意读题,子数组的数字都是连续的。

解题思路:

某个连续的子区间 ,只有在有且仅有一个数字x%g!=0的时候,才能满足条件。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

// 二分查找函数,用于查找第一个大于 num 的位置
ll pos(ll l,ll r,ll num){
    ll mid;
    while(l<=r){
        mid=(l+r)/2;
        if(vis[mid]>num) // 如果 vis[mid] 大于 num,则缩小右边界
            r=mid-1;
        else // 否则,缩小左边界
            l=mid+1;
    }
    return l; // 返回第一个大于 num 的位置
}

int main(){
    ios::sync_with_stdio(0); // 加速输入输出
    cin.tie(0); // 解绑 cin 和 cout,提高效率
    cout.tie(0); // 解绑 cin 和 cout,提高效率

    ll n, g; 
    cin >> n >> g; // 输入数组长度 n 和目标公约数 g

    // vis 数组,用于记录每个位置前有多少个不满足能被 g 整除的元素
    for(ll i = 1; i <= n; i++){
        ll x;
        cin >> x; // 输入每个元素
        if(x % g) // 如果元素 x 不能被 g 整除
            vis[i] = 1; // 标记 vis[i] 为 1,表示不符合条件
        vis[i] += vis[i - 1]; // 累加前缀和,记录从 1 到 i 的元素中不满足条件的个数
    }

    ll sum = 0; // 用于存储符合条件的子数组的个数
    // 遍历每个起始位置 i
    for(ll i = 1; i < n; i++){
        // 使用二分查找来查找符合条件的区间
        sum += (pos(i + 1, n, vis[i - 1] + 1) - i - 1); 
        // 这里我们使用 pos 查找满足 vis[j] > vis[i-1] + 1 的第一个 j 索引
        // 然后减去 i 和 1,得到符合条件的子数组个数
    }

    cout << sum; // 输出结果
    return 0;
}

 G.数组个数

提供一篇非常暴力,但非常好想的 O(nV5) 三维 DP 题解。

发现环这种情况很难搞,考虑如何去掉环的影响。

发现序列尾最多只会用到序列头的两个元素,那么枚举这两个元素,做 V2 次 DP 即可。

然后,我们设 dpi,j,k​ 为考虑序列的前 i 个数,最后两个数为 j,k,满足 b2​ 到 bi−1​ 限制的方案数。

转移十分简单,三重循环枚举 ai−2​,ai−1​,ai​ 这三个元素,如果满足了 bi−1​ 的限制那么即可转移。

最后,在 dpn,i,j​ 里面找答案,如果 i,j 能与我们枚举的前两个数一起,满足 b1​ 和 bn​ 的限制,那么即可统计入答案。

枚举前两个数复杂度 O(V2),单次 DP 复杂度 O(nV3),总复杂度为 O(nV5)。由于全都是简单的循环枚举,常数较小,可以通过此题。

代码如下:

#include<bits/stdc++.h>
using namespace std;
const int N=1005,M=12,P=1e9+7;
int n,ans,b[N],dp[N][M][M];
void DP(int x,int y){
	memset(dp,0,sizeof(dp));
	dp[2][x][y]=1; 
	for(int i=3;i<=n;++i){
		int lim=b[i-1];
		for(int j=0;j<=lim;++j){
			for(int k=0;k<=lim;++k){
				for(int f=0;f<=lim;++f){
					if(max(max(j,k),f)!=lim) continue;//枚举三个数,满足限制时可转移
					dp[i][j][k]+=dp[i-1][f][j];
					dp[i][j][k]%=P;
				}
			}
		}
	}
	for(int i=0;i<=10;++i){
		for(int j=0;j<=10;++j){
			if(max(max(i,j),x)==b[n]&&max(max(j,x),y)==b[1])
				ans=(ans+dp[n][i][j])%P;//如果满足头尾限制,更新答案
		}
	}
}
int main(){
	cin>>n;
	for(int i=1;i<=n;++i) cin>>b[i];
	for(int i=0;i<=10;++i) {
		for(int j=0;j<=10;++j){
			DP(i,j); //枚举前两个数DP
		}	
	}
	cout<<ans;
	return 0;
}

H.六六大顺

这题肯定爆,打个表找找规律。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll a[10];

int main(){
	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
	a[0]=0;//i=20时候就爆了
	for(ll i=1;i<10;i++){
		a[i]=a[i-1]*10+6;
		cout<<a[i]*a[i]<<'\n';
	}
	return 0;
}

        

错误思路: 

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N = 1e7 + 1;
/*
  S= 6*6 + 66*66 + 666*666 +...
  S= ((10-1)*2/3 )*((10-1)*2/3)+
  S= (10^i -1)^2 *4/9
  S= 4/9 *(10^(2i) +1 -2 *10^i)
  
 */
//高精度加法
int main(){
	ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
	ll n; cin >> n;
	vector<char> num1;
	num1.push_back('0');
	for(ll i=1;i<=n;i++){
		num1.push_back('0');
		num1.push_back('1');
	}
	
	//for(ll i=0;i<2*n+1;i++)cout<<num1[i];
	//cout<<'\n';
	
	vector<char> num2;
	num2.push_back('0');
	for(ll i=1;i<=n;i++)num2.push_back('2');
	
	//for(ll i=0;i<n+1;i++)cout<<num2[i];
	//cout<<'\n';
	
	vector<ll> res;
	ll jinwei=0;
	for(ll i=0;i<n+1;i++){
		ll cur=jinwei+(num1[i]-'0'+num2[i]-'0');
		res.push_back(cur%10);
		if(cur>=10)jinwei=cur/10;
		else jinwei=0;
	}
	
	//for(ll i=0;i<n+1;i++)cout<<res[i];
	//cout<<'\n';
	
	if(jinwei>=10)jinwei=jinwei/10;
	else jinwei=0;
	for(ll i=n+1;i<2*n+1;i++){
		ll cur=jinwei+num1[i]-'0';
		res.push_back(cur%10);
		if(cur>=10)jinwei=cur/10;
		else jinwei=0;
	}
	//for(ll i=n+1;i<2*n+1;i++)cout<<res[i];
	//cout<<'\n';
	//for(ll i=res.size()-1;i>=0;i--)cout<<res[i];
	
	//把n加里面再*4/9;
	vector<ll> num3;
	while(n){
		num3.push_back(n%10);
		n/=10;
	}
	
	vector<ll> ans;
	jinwei=0;
	for(ll i=0;i<num3.size();i++){
		ll cur=jinwei+res[i];
		ans.push_back(cur%10);
		if(cur>=10)jinwei=cur/10;
		else jinwei=0;
	}
	if(jinwei>=10)jinwei=jinwei/10;
	else jinwei=0;
	for(ll i=num3.size();i<res.size();i++){
		ll cur=jinwei+res[i];
		ans.push_back(cur%10);
		if(cur>=10)jinwei=cur/10;
		else jinwei=0;
	}
	
	//乘以4/9也是个坏事,难搞,这种思路不可行
	return 0;
}

 正确思路:

#include <stdio.h>
using namespace std;
const int MAX = 2e7 + 1;
int n, arr[MAX];//arr模拟高精数组
int main()
{
    scanf("%d", &n);
    arr[0] = 4 * n % 10, arr[1] = 4 * n / 10;//将4n存入数组
    for (int i = 1; i <= n; ++i)//++i卡常小妙招(bushi
	{
        arr[i << 1] = 4, arr[i] -= 8;//将1010…100,22…20的4倍加给数组
        arr[i + 1] += arr[i] / 10, arr[i] %= 10;
        if (arr[i] < 0)
        {
            arr[i] += 10, --arr[i + 1];
        }//进退位
    }
    for (int i = n + 1; arr[i] < 0; ++i)
    {
        arr[i + 1] += arr[i] / 10, arr[i] %= 10;
        arr[i] += 10, --arr[i + 1];
    }//单纯进位 
    for (int i = 2 * n - 1; ~i; --i)
	{
        arr[i] += 10 * arr[i + 1];
        putchar(arr[i] / 9 + '0');
        arr[i] %= 9;
    }//一边输出一边除以9 
    return 0; 
}

正确答案:

#include <bits/stdc++.h>

const int MX = 2e7 + 23;

using namespace std;

int n;
long long s[MX];

int main() {
  std::cin >> n;
  for (int i = 1; i <= n; ++i) {
    s[1] += 36;
    s[i + 1] -= 2 * 36;
    s[2 * i + 1] += 36;
  }

  for (int i = 1; i < MX ; ++i)
    s[i] += s[i - 1];
  for (int i = 1; i < MX ; ++i)
    s[i] += s[i - 1];
  

  int MAX = 0;
  for (int i = 1; i < MX; ++i) {
    s[i + 1] += s[i] / 10;
    s[i] %= 10;
    if (s[i]) MAX = i;
  }
  for (int i = MAX; i >= 1; --i)
    cout << s[i];
  cout << endl;
  return 0;
}

I.打折

解题思路: 记录所有物品在哪一天开始打折,在哪一天结束打折。

离散化所有时间,先暴力所有物品未打折的最小值(使用 multiset 快速找出),再枚举每一天,计算贡献,快速得出当天花费的最小值,最终即可得出答案。

代码有注释。

注意事项: 打折时间为 [s,t],意味着第 t+1 天才结束打折。

#include <bits/stdc++.h>
using namespace std;
 
// 2023 OneWan
 
const int MAXM = 100000 + 5;
int s[MAXM], t[MAXM], p[MAXM], c[MAXM]; // 对应题目输入的各数组
multiset<long long> st[MAXM]; // st[i] 为 物品 i 在所有商店的价格
vector<vector<pair<int, int>>> v(MAXM); // v[i][j] 为 商店 i 出售的 第 j 个物品

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    int n, m;
    cin >> n >> m;
    vector<int> time; // 用于离散化 时间
    for (int i = 0 ; i < m ; i++) {
        cin >> s[i] >> t[i] >> p[i] >> c[i];
        time.emplace_back(s[i]);
        time.emplace_back(t[i] + 1); // 打折是闭区间 所以需要+1才是没有打折截止
        for (int j = 0 ; j < c[i] ; j++) {
            int a, b; // 物品编号及原价
            cin >> a >> b;
            v[i].emplace_back(a, b);
        }
    }
    sort(time.begin(), time.end()); // 排序时间
    time.resize(unique(time.begin(), time.end()) - time.begin()); // 离散化时间
    auto get = [&](int t) {
        return lower_bound(time.begin(), time.end(), t) - time.begin();
    }; // 获取离散化后的下标
    int len = time.size();
    vector<vector<pair<int, int>>> startD(len), endD(len);
    // startD[i] 为 第 i 天 开始打折的物品编号 和 打折后的价格
    // endD[i] 为 第 i 天 结束打折的物品编号 和 打折后的价格
    for (int i = 0 ; i < m ; i++) {
        int starts = get(s[i]), ends = get(t[i] + 1); // 获取打折开始与结束时间离散化后的下标
        for (auto& [x, y] : v[i]) {
            int t = 1LL * y * p[i] / 100; // 打折后的价格
            st[x].insert(y); // 把物品原价放入
            startD[starts].emplace_back(x, t);
            endD[ends].emplace_back(x, t);
        }
    }
    long long temp = 0; // 用于存每天购买所有物品所用的价格
    for (int i = 1 ; i <= n ; i++) temp += *st[i].begin(); // 计算不进行打折时购买所有物品所用的价格
    long long ans = temp;
    for (int i = 0 ; i < len ; i++) {
        long long k = 0; // 打折与不打折对价格的贡献
        for (auto& [x, y] : startD[i]) { // 遍历当天所有开始打折的物品 打折前价格最小值为a, 打折后价格最小值为b, 贡献为b - a
            k -= *st[x].begin();
            st[x].insert(y);
            k += *st[x].begin();
        }
        for (auto& [x, y] : endD[i]) { // 遍历当天所有结束打折的物品 打折前价格最小值为a, 打折后价格最小值为b, 贡献为b - a
            k -= *st[x].begin();
            int t = st[x].count(y);
            st[x].erase(y);
            for (int j = 1 ; j < t ; j++) st[x].insert(y);
            k += *st[x].begin();
        }
        temp += k; // 加上贡献, 由前一段转移到后一段
        ans = min(ans, temp); // 找花费最小
    }
    cout << ans;
    return 0;
}

J.替换字符 

 

线段树..... 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值