2022藍橋杯大賽軟件賽省賽C/C++ 大學 A 組


第十三屆藍橋杯大賽軟件賽省賽C/C++ 大學 A 組

紀念我參加的第一次藍橋杯

2022年4月9日 9:00 - 13:00
這次藍橋杯因為疫情,在線上舉行

聽學長學姐們說藍橋杯又叫“送錢杯”,省一有手就行
那我就在這里先求一個省一吧!
2k 獎學金!求求了!

因為鄙人才學不高,所以這份題解中的解法難免有紕漏之處,還望各路神犇指出,鄙人將感激不盡。

題目鏈接

題目pdf

試題A: 裁紙刀

我的思路

考慮記憶化搜索。
后來聽說怎么剪都是一樣的???
int mem[n][m]為有 n n n m m m列個二維碼時,需要剪多少次(不考慮邊框)
於是遞歸公式為
m e m [ n ] [ m ] = min ⁡ { min ⁡ 1 ≤ i ≤ n − 1 { m e m [ i ] [ m ] + m e m [ n − i ] [ m ] + 1 } , min ⁡ 1 ≤ i ≤ m − 1 { m e m [ n ] [ i ] + m e m [ n ] [ m − i ] + 1 } } mem[n][m] = \min\{\min_{1 \leq i \leq n-1}\{mem[i][m]+mem[n-i][m]+1\}, \min_{1 \leq i \leq m-1}\{mem[n][i]+mem[n][m-i]+1\}\} mem[n][m]=min{1in1min{mem[i][m]+mem[ni][m]+1},1im1min{mem[n][i]+mem[n][mi]+1}}
最后答案是 m e m [ 20 ] [ 22 ] + 4 = 443 mem[20][22] + 4 = 443 mem[20][22]+4=443

我的代碼

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

const int inf = 1 << 30;
int mem[30][30];

int cut(int n, int m)
{
    if(n == 1 && m == 1) return 0;
    if(mem[n][m]) return mem[n][m];
    int r1 = inf;
    for(int i = 1; i < n; i++)
        r1 = min(r1, cut(i, m) + cut(n-i, m) + 1);
    int r2 = inf;
    for(int j = 1; j < m; j++)
        r2 = min(r2, cut(n, j) + cut(n, m-j) + 1);
    return mem[n][m] = min(r1, r2);
}

int main()
{
    printf("%d\n", 4 + cut(20, 22));
    return 0;
}

試題B: 滅鼠先鋒

我的思路

這應該就是一個普通的0/1博弈(這個博弈的名字似乎叫sg博弈)
狀態一共就 2 8 2^8 28種,一點也不多。
最后答案應該是LLLV

我的代碼

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;

int f[300];

int rev(int st)
{
    if(f[st]) return f[st];
    f[st] = -1;
    for(int i = 0; i < 8; i++)
    {
        if(!(st & (1 << i)))
        {
            if(rev(st | (1 << i)) == -1)
            {
                f[st] = 1;
                break;
            }
        }
    }
    if(f[st] != 1) for(int i = 0; i < 7; i++) if(i != 3)
    {
        if(!(st & (3 << i)))
        {
            if(rev(st | (3 << i)) == -1)
            {
                f[st] = 1;
                break;
            }
        }
    }
    return f[st];
}

int main()
{
    f[0xff] = 1;
    //這里取負是因為,先手已經下過了,所以就后手贏先手就輸,后手輸先手就贏
    printf("%d\n", -rev(0b10000000));
    printf("%d\n", -rev(0b11000000));
    printf("%d\n", -rev(0b01000000));
    printf("%d\n", -rev(0b01100000));
    return 0;
}

試題C: 求和

我的思路

簽到題,預處理sum就可以了(而且這題還良心的不會爆long long),復雜度 O ( n ) O(n) O(n)

我的代碼

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;

const int maxn = 2e5 + 10;
int n, a[maxn];

long long sum_ = 0;
long long ans = 0;

int main()
{
    scanf("%d", &n);
    for(int i = 0; i < n; i++)
        scanf("%d", &a[i]);
    for(int i = 0; i < n; i++)
        sum_ += a[i];
    for(int i = 0; i < n; i++)
        ans += a[i] * (sum_ - a[i]);
    ans /= 2;
    printf("%lld\n", ans);
    return 0;
}

試題D: 選數異或

我的思路

這個題考場上想了好久好久,最后居然還是只寫了一個 O ( n 2 m ) O(n^2m) O(n2m)的暴力,只能得2分,我人傻了

之后突然發現可以離線……
於是對每個詢問的r排序,這個題就解決了

具體來說,就是開一個map<int,int> mp來存數字x出現的最晚的位置(由於 a i ≤ 2 20 a_i\leq 2^{20} ai220所以直接開數組也可以)
再令int near為最近的可以滿足要求的位置,初始化為0
然后從0開始遍歷整個數列,每次遍歷時更新near = max(near, mp[a[i]^x]),然后更新mp[a[i]] = i,然后處理所有r == i的詢問,使得他們的答案ans = (l >= near)

我的代碼

#include <map>
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
#define yesno(x) \ do { \ if(x) printf("yes\n"); \ else printf("no\n"); \ } while(0)

const int maxn = 1e5 + 10;
map<int, int> mp;
int n, m, x, a[maxn];

struct Ques
{
    int l, r, id;
    bool ans;
}q[maxn];

bool cmp_1(const Ques& p, const Ques& q)
{
    return p.r < q.r;
}

bool cmp_2(const Ques& p, const Ques& q)
{
    return p.id < q.id;
}

int main()
{
    scanf("%d%d%d", &n, &m, &x);
    for(int i = 1; i <= n; i++)
        scanf("%d", &a[i]);
    for(int i = 0; i < m; i++)
    {
        scanf("%d%d", &q[i].l, &q[i].r);
        q[i].id = i;
    }
    sort(q, q+m, cmp_1);
    int ptr = 0, near = 0;
    for(int i = 0; i < m; i++)
    {
        while(ptr <= q[i].r)
        {
            near = max(near, mp[a[ptr] ^ x]);
            mp[a[ptr]] = ptr;
            ptr++;
        }
        q[i].ans = (near >= q[i].l);
    }
    sort(q, q+m, cmp_2);
    for(int i = 0; i < m; i++)
    {
        yesno(q[i].ans);
    }
    return 0;
}

試題E: 爬樹的甲殼蟲

我的思路

期望dp
其實也不是dp
就是一個單純的遞推式:
E ( k ) = P ( k ) ∗ E ( 0 ) + ( 1 − P ( k ) ) ∗ E ( k + 1 ) + 1 E(k) = P(k)*E(0) + (1-P(k))*E(k+1) + 1 E(k)=P(k)E(0)+(1P(k))E(k+1)+1
顯然要逆向計算。
注意到逆向計算時E(0)是未知的,但是始終只會出現一次項
不妨直接開一個結構體(或者pair)來表示期望,結構體中就存兩個數:
一個是E(0)的系數,還有一個是常數
最后就遞推得到關於E(0)的一個一次方程,就能求出E(0)了

另外就是常規的小費馬定理求分數取模

我的代碼

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;

const int modp = 998244353;

int qpow(int base, int exp)
{
    if(!exp) return 1;
    if(exp & 1) return base * 1ll * qpow(base * 1ll * base % modp, exp >> 1) % modp;
    return qpow(base * 1ll * base % modp, exp >> 1);
}

const int maxn = 1e5 + 10;
int n, P[maxn];
struct ANS
{
    int r, t; // r是系數,t是常數;為什么用這兩個字母?我亂選的
    ANS() {}
    ANS(int _r, int _t) { r = _r; t = _t; }
    ANS operator * (const int ot) const
    {
        return ANS(r * 1ll * ot % modp, t * 1ll * ot % modp);
    }
    ANS operator + (const ANS &ot) const
    {
        return ANS((r + ot.r) % modp, (t + ot.t) % modp);
    }
} ans[maxn];

int main()
{
    scanf("%d", &n);
    for(int i = 0; i < n; i++)
    {
        int a, b;
        scanf("%d%d", &a, &b);
        P[i] = a * 1ll * qpow(b, modp - 2) % modp;
    }
    ans[n] = ANS(0, 0);
    for(int k = n - 1; k >= 0; k--)
    {
        ans[k] = ANS(P[k], 0) + ans[k+1] * ((1 - P[k] + modp) % modp) + ANS(0, 1);
    }
    printf("%lld\n", ans[0].t * 1ll * qpow((1 - ans[0].r + modp) % modp, modp - 2) % modp);
    return 0;
}

試題F: 青蛙過河

我的思路

顯然二分答案,關鍵是怎么進行check
這里我是貪心做的,不知道對不對。
也就是說每次都盡量往最遠的地方跳

我的代碼

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;

const int maxn = 1e5 + 10;
int n, x, h[maxn], h_copy[maxn];
long long cnt[maxn];

inline bool check(int y)
{
    for(int i = 1; i < n; i++)
        h_copy[i] = h[i];
    int far = n - 1; // 用來記錄當前可以到達的最遠的地方
    for(int i = n - 1; i > 0; i--)
    {
        cnt[i] = 0; // 考試的時候沒寫!我肯定寄了……
        if(i + y >= n)
        {
            cnt[i] = h_copy[i];
            continue;
        }
        far = min(far, i + y);
        while(far > i)
        {
            if(cnt[far] <= h_copy[i])
            {
                h_copy[i] -= cnt[far];
                cnt[i] += cnt[far];
                cnt[far] = 0;
                far--;
            }
            else
            {
                cnt[far] -= h_copy[i];
                cnt[i] += h_copy[i];
                h_copy[i] = 0;
                break;
            }
        }
    }
    cnt[0] = 0;
    for(int i = 1; i <= y; i++)
        cnt[0] += cnt[i];
    return cnt[0] >= 2 * x;
}

int main()
{
    scanf("%d%d", &n, &x);
    for(int i = 1; i < n; i++)
        scanf("%d", &h[i]);
    int l = 1, r = n;
    while(l != r)
    {
        int mid = l + r >> 1;
        if(check(mid))
            r = mid;
        else
            l = mid + 1;
    }
    printf("%d\n", l);
    return 0;
}

后記

2022年4月9日 20:55  
寫這篇題解的時候發現F題忘記初始化肯定寄了,我瞬間裂開,所以后面的題就以后再說吧


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM