2024蓝桥杯国赛C++B组题解

此文章背景:某菜鸡心血来潮想写蓝桥杯的题,结果发现有些题没有题解可以“抄”,于是菜鸡自己写了一篇题解。

A题、合法密码

按照要求暴力判断即可

B题、选数概率

本题可细分分为两个要求:

按照组合数公式得到第一个要求

C\binom{2}{S} = \frac{1}{2}*S*(S - 1)是10455的倍数。

第二个要求

a+b+c 最小,即S最小

综上,对S进行枚举,判断 C\binom{2}{S}是否是10455的倍数即可

C题、蚂蚁开会

考点:gcd ?

注意到数据范围为n <= 500,那么我们可以考虑 n^{2} 暴力枚举。

现在需要解决的唯一问题是这么得到交点为整点的点。

        再次观察数据范围,坐标的范围在[0, 10000],那么对于每个点,我们只需要从左下角的点开始,以gcd(abs(x1, x2), abs(y1, y2))的步长进行枚举,将枚举到的点加入到一个map集合中,最后遍历map即可。(代码写得有点笨)

#include<iostream>
#include<map>

using namespace std;
typedef pair<int,int> PII;
const static int N = 510;
PII a[N];
map<PII, int>mp;

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

void cal1(int x1, int y1, int x2, int y2){
    int a = abs(x2 - x1), b = abs(y2 - y1);
    int g = gcd(a, b);
    a /= g, b /= g;
    for(int i = x1, j = y1; i <= x2 && j <= y2; i += a, j += b){
        mp[{i, j}]++;
    }
}

void cal2(int x1, int y1, int x2, int y2){
    int a = abs(x2 - x1), b = abs(y2 - y1);
    int g = gcd(a, b);
    a /= g, b /= g;
    for(int i = x1, j = y1; i <= x2 && j >= y2; i += a, j -= b){
        mp[{i, j}]++;
    }
}

int main(){
    mp.clear();
    int n, x1, x2, y1, y2;
    cin >> n;
    for(int i = 0; i < n; i++){
        cin >> x1 >> y1 >> x2 >> y2;
        if(x1 > x2){
            swap(x1, x2);
            swap(y1, y2);
        }
        if(y1 < y2)
            cal1(x1, y1, x2, y2);
        else
            cal2(x1, y1, x2, y2);
    }
    int ans = 0;
    for(auto it : mp){
        if(it.second > 1)ans++;
    }
    cout << ans << endl;
    return 0;
}

D、立定跳远

考点:二分

题目要求我们找到单次跳跃最远距离 L 的最小值,触发关键词最小化最大值。那么我们可以考虑二分答案。

为什么?

因为有单调性——L越大,越能满足条件。而且最小化最大值是二分常见套路

现在令人蛋疼的问题就是有一次跳跃能跳2L。

那么显然只需要将跳跃次数增加1就行

简单证明:

我们可以在任意ai - ai-1 > L 的中间添加一个虚拟的检查点(临时合法停靠点),那么跳跃两次即可。

#include<iostream>

using namespace std;

const static int N = 100010;
int a[N], n, m;

bool check(int x){
    int t = m + 1;
    for(int i = 1; i <= n; i++){
        int sub = a[i] - a[i - 1] - 1;
        t -= max(0, sub / x);
        if(t < 0)return false;
    }
    return true;
}

int main(){
    cin >> n >> m;
    a[0] = 0;
    for(int i = 1; i <= n; i++)cin >> a[i];
    int l = 1, r = a[n - 1] - a[0];
    while(l < r){
        int mid = (l + r) >> 1;
        if(check(mid))r = mid;
        else l = mid + 1;
    }
    cout << l << endl;
    return 0;
}

E题、最小字符串

考点:贪心、双指针

题目要求我们得到字典序最小的字符串且M个小写字母能够插入到任意位置,那么显然我们要将M个小写字母先排序,然后双指针即可。

#include<iostream>
#include<string>
#include<algorithm>

using namespace std;

int main(){
    string a, b;
    int n, m;
    cin >> n >> m;
    cin >> a >> b;
    sort(b.begin(), b.end());
    string ans;
    int i = 0, j = 0;
    while(i < n || j < m){
        if(i == n)
            ans.push_back(b[j++]);
        else if(j == m)
            ans.push_back(a[i++]);
        else{
            if(a[i] <= b[j])
                ans.push_back(a[i++]);
            else
                ans.push_back(b[j++]);
        }
    }
    cout << ans << endl;
    return 0;
}

F题、数位翻转

考点:DP

把题意说成人话就是在一个数组中选择0~m段区间进行题目所说的操作,求操作后整个数组的最大和。

定义状态:f[i][j][k]表示前i个数,操作j段区间,k表示a[i]是否翻转(0表示不翻转a[i],1表示翻转a[i])的数组最大和。

答案:max(f[n][j][0], f[n][j][1])(0 <= j <= m),具体实现时为了方便边界处理(1 <= j <= m + 1)

各个维度作用:

第一个维度:......

第二个维度:限制最终的答案合法(只能操作0~m段区间)

第三个维度:为了进行状态合并。比如操作a[i]的时候,a[i - 1]是否进行了操作,进行了操作的话,

就需要将a[i]加入到a[i - 1]的区间中,这样才不会浪费可操作的区间段。因为后面可能需要翻转,

但是你这里浪费了次数(没有进行a[i - 1]和a[i]的合并),最后可能会导致答案错误。

菜鸡代码:

#include<iostream>
#define int long long

using namespace std;

const static int N = 1010;
int a[N], re[N], f[N][N][2];
int inf = -100000000;
int rev(int x){
    int ans = 0;
    while(x){
        if(x & 1){
            ans |= 1;
        }
        x >>= 1;
        ans <<= 1;
    }
    return ans >> 1;
}

signed main(){
    int n, m;
    cin >> n >> m;
    for(int i = 1; i <= n; i++)
        cin >> a[i], re[i] = rev(a[i]);
    for(int i = 0; i <= n; i++){
        for(int j = 0; j <= m; j++){
            f[i][j][0] = f[i][j][1] = inf;
        }
    }
    f[0][1][0] = 0;
    for(int i = 1; i <= n; i++){
        for(int j = 0; j <= min(i, m); j++){
            f[i][j + 1][0] = max(f[i - 1][j + 1][0], f[i - 1][j + 1][1]) + a[i];
            f[i][j + 1][1] = max(f[i - 1][j][0], f[i - 1][j + 1][1]) + re[i];
        }
    }
    int ans = 0;
    for(int i = 1; i <= m + 1; i++){
        ans = max(ans, max(f[n][i][0], f[n][i][1]));
    }
    cout << ans << endl;
    return 0;
}

G题、数星星

不会......

H题、套手镯

考点:贪心、双指针、扫描线、优先队列

给我们n个圆以及一个矩形,问我们在采取最优的放置矩形的策略下,完全在矩形范围内的圆的个数。

首先对于矩形的放置,我们有两个维度需要考虑——x,y。

因此我们需要先固定一维,然后枚举另一个维度。(扫描线的体现)

发现x,y的范围是令人蛋疼的1e8,不能直接暴力枚举。

那么怎么固定呢?

其实仔细思考一下,在最优的矩形放置策略中,我们是不是将矩形的底部刚好与某个圆的底部相切是最优的。其实显然,因为上诉操作会尽可能的留空间给上面的圆形(这也是我认为考点有贪心的点)。因此,对于固定的y,我们只需要枚举每个圆的最底部即可。

先将圆按照右端点升序排序!!!

然后就是双指针,对于x这个维度我们也需要像维度y一样贪心地考虑。

因此,我们需要用一个优先队列对在矩形中的圆的最左端点进行维护,这样在遍历右端点的时候我们可以及时且高效地将左端点越出矩形左边界的点删除。然后依次遍历维护最大点数(优先队列的大小的最大值)即可。

菜鸡代码:

#include<iostream>
#include<algorithm>
#include<queue>

using namespace std;

const static int N = 1010;
struct node{
    int x, y, r;
    bool operator <(node a){
        return x + r < a.x + a.r;
    }
}p[N];

int ys[N], n, w, h;

int slv(int ed){
    priority_queue<int, vector<int>, greater<int>>q;
    int ans = 0;
    for(int i = 0; i < n; i++){
        //invalid
        if(p[i].y - p[i].r < ed || p[i].y + p[i].r > h + ed)continue;
        if(2 * p[i].r > w)continue;
        int last = p[i].x + p[i].r;
        while(q.size() && last - q.top() > w){
            q.pop();
        }
        int l = p[i].x - p[i].r;
        q.push(l);
        ans = max(ans, (int)q.size());
    }
    return ans;
}

int main(){
    cin >> n >> w >> h;
    for(int i = 0; i < n; i++){
        cin >> p[i].x >> p[i].y >> p[i].r;
        ys[i] = p[i].y - p[i].r;
    }
    sort(p, p + n);
    sort(ys, ys + n);
    int ans = 0;
    
    for(int i = 0; i < n; i++){
        ans = max(ans, slv(ys[i]));
    }

    swap(w, h);
    for(int i = 0; i < n; i++){
        ans = max(ans, slv(ys[i]));
    }
    cout << ans << endl;
    return 0;
}

I题、跳石头

考点:DFS、图、bitset优化

本体最主要的就是集合之间快速求并集,如果按照朴素方法时间复杂度将会达到O(n^2),

在n <= 40000的情况下会超时。因此考虑优化集合之间求并集这一操作。

bitset是个好东西,通过bitset可以快速求两个集合之间的并集。

直接上菜鸡代码:

#include<iostream>
#include<vector>
#include<bitset>
#include<map>

using namespace std;

const static int N = 40010;

int c[N], n, ans;
vector<int>e[N];
vector<bitset<N>>f;
map<int,int>mp;

void dfs(int u){
    f[u][c[u]] = 1;
    mp[u]++;
    for(auto i : e[u]){
        if(!mp.count(i))
            dfs(i);
        f[u] |= f[i];
    }
    ans = max(ans, (int)f[u].count());
}

int main(){
    cin >> n;
    ans = 0;
    f.resize(n + 1);
    for(int i = 1; i <= n; i++)cin >> c[i];
    for(int i = 1; i <= n; i++){
        int ne = i + c[i];
        if(ne <= n)e[i].push_back(ne);
        int ne1 = 2 * i;
        if(ne1 != ne && ne1 <= n)e[i].push_back(ne1);
    }
    mp.clear();
    for(int i = 1; i <= n ;i++){
        if(!mp.count(i))
            dfs(i);
    }
    cout << ans << endl;
    return 0;
}

J题、最长回文前后缀

考点:KMP、贪心

首先读懂题,这题本人感觉挺绕,但其实并不难(会KMP的前提下)。

然后就直接上代码了。

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

using namespace std;

int cal(string&s, string& t){
    string p = s + "#" + t;
    int n = p.length(), m = s.length(), ans = 0;
    vector<int>ne(n + 1);
    ne[0] = 0;
    for(int i = 1, j = 0; i < n; i++){
        while(j > 0 && p[i] != p[j]) j = ne[j - 1];
        if(p[i] == p[j])j++;
        ne[i] = j;
        if(i > m && i - m + ne[i] <= m)ans = max(ans, ne[i]);
    }
    return ans;
}

int main(){
    string s;
    cin >> s;
    int n = s.length();
    int l = 0, r = n - 1;
    while(l <= r && s[l] == s[r])l++, r--;
    s = s.substr(l, n - 2 * l);
    string t = s;
    reverse(t.begin(), t.end());
    int len = 0, ans = l;
    len = max(cal(s, t), cal(t, s));
    cout << ans + len << endl;
    return 0;
}

第一次写题解%%%%

希望能帮助到大家,最后一起共勉!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值