2024年第十五届蓝桥杯大赛软件赛省赛C/C++大学C组真题

打卡第二篇。

A.拼正方形

#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 a=7385137888721;
	ll b=10470245;
	a=(ll)sqrt(a);
	ll shengyu=7385137888721-a*a;
	cout<<a<<" "<<"shengyu="<<shengyu<<endl;
	cout<<((shengyu>=b)?"shengyu":"b")<<endl;
	ll i=a+1;
	b-=((2*i-1-shengyu)*4);//1个2X2=4个1X1
	ll temp2=b;
	while(temp2>=0){
		temp2-=((2*i-1)*4);
		i++;
	}
	cout<<i*2-1<<endl;
	return 0;
}
#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 a=7385137888721;
	ll b=10470245;
	ll sum=b+4*a;
	ll res=(ll)sqrt(sum);
	cout<<res;
	return 0;
}

答案是5435123,把2*2的正方形数量乘以4,相当于把一个2*2看成4个1*1,再加上1*1正方形的面积可以得出能拼出的总面积,再开二次根向下取整得到结果 。

B.劲舞团 

答案:9 。

C.数字诗意

暴力枚举查找规律 ,发现奇数除了1都可以,因为任意奇数都可以拆成一个偶数+奇数的形式,题干也说了,(至少两个)。偶数除了2^n都可以。

#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 n;cin>>n;
	vector<ll> a(n);
	ll count=0;
	for(ll i=0;i<n;i++){
		cin>>a[i];
		if(a[i]==1)continue;
		for(ll j=1;j<=54;j++){
			if(pow(2,j)==a[i]){
				count++;
				break;
			}
		}
	}
	cout<<count;
	return 0;
}
/*
  S=(i+1)*i/2;
  1,2 不可以
  3=1,2
  4  
  5=2,3
  6=1,2,3
  7=3 4奇数都可以
  8
  10=1,2,3,4
  12=3 4 5
  14=2,3,4,5
  16=
 */

D.封闭图形个数 

如果cmp函数传的参数是ll &x,ll &y,代码将运行不起来 。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll tongji(ll num){
	ll t=num;
	ll sum=0;
	while(t){
		ll cur=t%10;
		if(cur==0||cur==4||cur==6||cur==9)sum+=1;
		else if(cur==8)sum+=2;
		else sum+=0;
		t/=10;
	}
	return sum;
}
bool cmp(pair<ll,ll> &x,pair<ll,ll> &y){
	if(x.first==y.first)return true;
	else{
		if(x.second==y.second)return x<y;
		else return x.second<y.second;
	}
}
int main(){
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	ll n;cin>>n;
	vector<pair<ll,ll>> a(n,{0,0});
	for(ll i=0;i<n;i++){
		cin>>a[i].first;
		a[i].second=tongji(a[i].first);
	}
	sort(a.begin(),a.end(),cmp);
	for(ll i=0;i<n;i++)cout<<a[i].first<<" ";
	return 0;
}

E.回文数组 (java组)

不小心多加一个题,算了不删了 ,多多益善。

这题目老演员了。

#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 n;cin>>n;
	vector<ll> a(n+1,0);
	for(ll i=1;i<=n;i++)cin>>a[i];
	vector<ll> feet(n+2,0);//防止越界,设n+1也可以过,即使i<=n
	for(ll i=1;i<=n/2;i++){///(n+1)/2也可以过
		if(a[i]>a[n-i+1])feet[i]=a[i]-a[n-i+1];
		//a[i]大些,a[i]要变小,a[n-i+1]不用变
		else feet[n-i+1]=a[n-i+1]-a[i];
	}
	ll ans=0;
	for(ll i=1;i<=n;i++){
		ll cur=min(feet[i],feet[i+1]);
		feet[i]-=cur;
		feet[i+1]-=cur;
		//ll danci=(feet[i]>feet[i+1])?(feet[i]-cur):(feet[i+1]-cur);
		ans+=(cur+feet[i]);//当前值是feet[i],不是feet[i+1],feet[i]==0时,ans+=(cur+danci)就错了
	}
	cout<<ans;
	return 0;
}

F.商品库存管理 

超时代码: 

#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 n,m;cin>>n>>m;
	vector<pair<ll,ll>> op(m+1,{0,0});
	for(ll i=1;i<=m;i++){
		cin>>op[i].first>>op[i].second;
	}
	for(ll i=1;i<=m;i++){
		ll st=INT_MAX;ll ed=0;
		for(ll j=1;j<=m;j++){
			if(j==i)continue;
			st=min(st,op[j].first);
			ed=max(ed,op[j].second);
		}
		ll ans=n-(ed-st+1);
		cout<<ans<<'\n';
	}
	return 0;
}

正确代码: 

#include <bits/stdc++.h>
using namespace std;
const int N = 300005;

int n, m;
int d[N];               // 最终每个商品的覆盖次数
int prefix_zero[N];     // 前缀和,记录库存量为0的商品数量
int prefix_one[N];      // 前缀和,记录库存量为1的商品数量
int diff[N];            // 差分数组
int l[N], r[N];         // 每个操作的区间

int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= m; i++) {
        scanf("%d%d", &l[i], &r[i]);
        diff[l[i] - 1]++;  // 差分数组更新
        diff[r[i]]--;
    }

    // 计算每个商品的覆盖次数
    for (int i = 1; i <= n; i++) {
        d[i] = d[i - 1] + diff[i - 1];
    }

    // 计算前缀和
    for (int i = 1; i <= n; i++) {
        prefix_zero[i] = prefix_zero[i - 1] + (d[i] == 0);
        prefix_one[i] = prefix_one[i - 1] + (d[i] == 1);
    }

    // 计算结果
    for (int i = 1; i <= m; i++) {
        int L = l[i], R = r[i];
        int zero_count = prefix_zero[L - 1] + (prefix_one[R] - prefix_one[L - 1]) + (prefix_zero[n] - prefix_zero[R]);
        printf("%d\n", zero_count);
    }

    return 0;
}

G.挖矿 

前缀和:

​
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N=2e6+10;//一定要开大点不然越界
int main(){
	ll n,m;cin>>n>>m;
	vector<ll> a(N,0),l(N,0),r(N,0);
	ll cnt=0;
	for(ll i=1;i<=n;i++){
		cin>>a[i];
		if(a[i]>0)r[a[i]]++;
		else if(a[i]<0)l[-a[i]]++;
		else cnt++;
		//l[i]+=l[i-1];
		//r[i]+=r[i-1];
	}
	for(ll i=1;i<=m;i++){
		l[i]+=l[i-1];
		r[i]+=r[i-1];
	}
	/*
	sort(a.begin(),a.end());
	for(ll i=1;i<l.size();i++)cout<<l[i]<<" ";
	cout<<endl;
	for(ll i=1;i<r.size();i++)cout<<r[i]<<" ";
	cout<<endl;
	 */
	ll feet=0;
	for(ll i=1;i<=m;i++){
		ll t=l[i];
		if(m-2*i>0)t+=r[m-2*i];
		feet=max(feet,t);
		t=r[i];
		if(m-2*i>0)t+=l[m-2*i];
		feet=max(feet,t);
	}
	cout<<feet+cnt<<'\n';
	return 0;
}

​

解题思路:

如果我们在数轴上要“同时”去左边的矿洞和右边的矿洞,往返经过原点就会消耗距离,因此不难证明,转弯 1 次和 0 次的方案才可能是最优解。因此,只有四种可能的方案:正方向走到头、反方向走到头、反方向走 t格然后往正方向走到头、正方向走 t 格然后往反方向走到头。

发现前两个方案可以与后两个方案合并。

  • 统计并离散化:

    • 读入所有矿洞坐标;
    • l[x] 记录“坐标是负数且绝对值为 xxx 的矿洞数”,用 r[x] 记录“坐标是正数且数值为 xxx 的矿洞数”;
    • 额外用 cnt 记录“坐标为 0 的矿洞数”。
  • 前缀和:

    • 构造 l[i] 的前缀和,表示负坐标绝对值 ≤i 的总矿洞数;
    • 构造 r[i] 的前缀和,表示正坐标数值 ≤i 的总矿洞数;
    • 这样,只要我们能走到左边最远距离是 d,就可以直接得到挖到的负坐标矿洞数 = l[d];走到右边最远距离是 d,挖到的正坐标矿洞数 = r[d]
  • 枚举左/右的“最远距离”

    • 考虑先去左边,再去右边:如果先向左走 i 步,再回到 0,就走了 2i 步,还剩 m−2i  步可以向右。能挖到矿的数量是 l[i]  +  r(m−2i)(若 m−2i≥0才有效) 。
    • 考虑先去右边,再去左边:若先向右走 i 步,再回到 0,也走了 2i 步,还剩 m−2i  步向左,能挖到矿的数量是 r[i]  +  l(m−2i) 
    • 我们遍历 i从 1 到 m,把这两种情况的最大值取出来。
  • 加上坐标为 0 的矿洞

    • 因为坐标为 0 的矿洞可以直接挖到,不用走路,所以把上面最大值再加上 cnt 即是答案。

 H.吊坠

Oh,my god,最小生成树。。。。我还不会。。以后学了来补题。 

答案代码:

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

/*
 * 本题思路:
 * 1. 输入 n, m,以及 n 个长度为 m 的环形字符串(只能旋转,不可翻转)。
 * 2. 将每个长度 m 的字符串 s[i] 扩展成长 2*m 的串 arr[i] = s[i] + s[i],以便模拟“环形旋转”。
 * 3. 对每对字符串 (i, j),计算它们的最长公共子串(LCSubstr)长度,记为 dist[i][j]。
 *    - 注意:因为是环形,arr[i]、arr[j] 均为 2*m 长,但最终的公共子串长度最多不能超过 m。
 * 4. 建立一张完全图:每个字符串视为一个顶点,(i, j) 的边权 = dist[i][j]。
 * 5. 在这张完全图上,用 Kruskal 算法选取 n-1 条边,使所有顶点连通且边权和最大。
 * 6. 输出最大边权和。
 */

static const int MAXN = 200; // n <= 200
static const int MAXM = 50;  // m <= 50

// ---------------- 并查集 (Union-Find) ---------------- //
struct UnionFind {
    vector<int> parent, rank_;
    UnionFind(int n) : parent(n), rank_(n, 0) {
        for (int i = 0; i < n; i++) parent[i] = i;
    }
    int findSet(int x) {
        if (parent[x] != x) {
            parent[x] = findSet(parent[x]);
        }
        return parent[x];
    }
    void unionSet(int x, int y) {
        int rx = findSet(x), ry = findSet(y);
        if (rx != ry) {
            // 按秩合并
            if (rank_[rx] < rank_[ry]) swap(rx, ry);
            parent[ry] = rx;
            if (rank_[rx] == rank_[ry]) rank_[rx]++;
        }
    }
};

// ---------------- 计算两环形串的最长公共子串 ---------------- //
/*
 * arr[x], arr[y] 分别是原串 s[x], s[y] 各自拼接自身后得到的长度 2*m 的字符串。
 * 我们用一个 DP (Longest Common Substring):
 *   dp[i][j] 表示以 arr[x][i-1], arr[y][j-1] 结尾的公共子串长度。
 * 时间复杂度 O((2*m)^2) = O(4*m^2)。由于 n 最多 200, m 最多 50,可以接受。
 */
int lcs(const string &sx, const string &sy, int m)
{
    // sx.size()、sy.size() 都是 2*m
    int len = sx.size();  // = 2*m
    vector<vector<int>> dp(len + 1, vector<int>(len + 1, 0));

    int maxLen = 0;
    for(int i = 1; i <= len; i++){
        for(int j = 1; j <= len; j++){
            if(sx[i-1] == sy[j-1]) {
                dp[i][j] = dp[i-1][j-1] + 1;
                maxLen = max(maxLen, dp[i][j]);
            }
        }
    }
    // 最长公共子串不能超过原环的实际长度 m
    return min(maxLen, m);
}

// ---------------- 主函数 ---------------- //
int main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n, m;
    cin >> n >> m;

    // 原输入的 n 个环形字符串,长度均为 m
    vector<string> s(n);
    for(int i = 0; i < n; i++){
        cin >> s[i];
    }

    /*
     * 将每个字符串 s[i] 拼接自己得到 arr[i],长度 = 2*m。
     * 这样 arr[i] 中任取长度 m 的连续子串都对应环形串的一种旋转。
     */
    vector<string> arr(n);
    for(int i = 0; i < n; i++){
        arr[i] = s[i] + s[i]; // 拼接自己
    }

    // 预先计算每对 (i, j) 的“环形最长公共子串”长度。
    // 这里 dist[i][j] = dist[j][i],图是无向的。
    vector<vector<int>> dist(n, vector<int>(n, 0));
    for(int i = 0; i < n; i++){
        for(int j = i + 1; j < n; j++){
            dist[i][j] = lcs(arr[i], arr[j], m);
            dist[j][i] = dist[i][j];
        }
    }

    // 将所有边 (i, j) 存入一个边集,边权 = dist[i][j]。
    // Kruskal 需要从大到小排序(求最大生成树)。
    vector<array<int,3>> edges; 
    edges.reserve(n*(n-1)/2);

    for(int i = 0; i < n; i++){
        for(int j = i+1; j < n; j++){
            edges.push_back({dist[i][j], i, j});  
        }
    }
    // 按边权从大到小排序
    sort(edges.begin(), edges.end(), [](auto &a, auto &b){
        return a[0] > b[0];  // 降序
    });

    // Kruskal 算法:选 n-1 条边连通所有顶点,并使边权和最大
    UnionFind uf(n);
    int chosen = 0;
    long long ans = 0;
    for(auto &e : edges) {
        int w = e[0], x = e[1], y = e[2];
        if(uf.findSet(x) != uf.findSet(y)) {
            uf.unionSet(x, y);
            ans += w;
            chosen++;
            if(chosen == n - 1) break;
        }
    }

    cout << ans << "\n";
    return 0;
}

I.回文字符串

这c语言网真的服了,看看提交的时间点就知道了 。。。luogu都对了,我哥们说c语言网经常这样,见怪不怪了。。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
bool check(string s){
	ll l=0,r=s.length()-1;
	while(l<=r){
		if(s[l]!=s[r])return false;
		l++;r--;
	}
	return true;
}
int main(){
	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
	ll t;cin>>t;
	while(t--){
		bool res=false;
		string s;cin>>s;
		if(check(s))res=true;
		else{
			ll l=0;
			while(l<s.length()&&(s[l]=='l'||s[l]=='q'||s[l]=='b'))l++;
			ll r=s.length()-1;
			while(r>l&&(s[r]=='l'||s[r]=='q'||s[r]=='b'))r--;
			string str=s.substr(l,r-l+1);
			reverse(str.begin(),str.end());
			if(str!=s.substr(l,r-l+1))res=false;
			else{
				while(l>=0&&r<s.length()&&s[l]==s[r])l--,r++;
				if(l==-1)res=true;
				else res=false;
			}
		}
		if(res==false)cout<<"No"<<'\n';
		else cout<<"Yes"<<'\n';
	}
	return 0;
}

结束! 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值