第十二届蓝桥杯国赛 cb

A: 带宽 25

B: 纯质数 1903

考场上用的欧拉筛,这里用埃氏筛。

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

const int N = 20210606;
bool vis[N];
vector<int> primes;
void init()
{
    int i;
    for (i = 2; i < N; ++i) {
        if (!vis[i]) primes.push_back(i);
        for (int j = 0; i*primes[j] < N; ++j) {
            vis[i*primes[j]] = 1;
            if (i%primes[j] == 0) break;
        }
    }
}

int main()
{
    init();
    int ans = primes.size();
    for (auto x : primes) {
        while (x) {
            int t = x%10;
            if (t != 2 && t != 3 && t != 5 && t != 7) {
                --ans; break;
            }
            x /= 10;
        }
    }
    cout << ans; // 1903
    return 0;
}

C: 完全日期 977

不熟悉 p y t h o n python python 的日期类,怕用错,于是手动模拟日期变化。

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

class Date
{
    int y, m, d;
public:
    Date(int y, int m, int d) : y(y), m(m), d(d) {}
    void add() {
        int nums[] = {0,31,28,31,30,31,30,31,31,30,31,30,31};
        if (y%400 == 0 || y%100 && y%4 == 0) ++nums[2];
        if (++d > nums[m]) {
            d = 1;
            if (++m > 12) m = 1, ++y;
        }
    }
    bool operator == (const Date &t) const {
        return y == t.y && m == t.m && d == t.d;
    }
    bool valid() {
        auto sum = [] (int x) {
            int ret = 0;
            while (x) ret += x%10, x /= 10;
            return ret;
        };
        int t = sum(y)+sum(m)+sum(d);
        int i = 0;
        while (i*i < t) ++i;
        return i*i == t;
    }
};

int main()
{
    Date s(2001, 1, 1), t(2022, 1, 1);
    int ans = 0;
    while (!(s == t)) {
        ans += s.valid();
        s.add();
    }
    cout << ans;  // 977
    return 0;
}

D: 最小权值 2653631372

题目直接给出状态转移方程,感觉 d p dp dp 的暗示还是蛮明显的。

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

typedef long long LL;
LL dp[2022];
int main()
{
    for (int i = 1; i <= 2021; ++i) {
        dp[i] = LLONG_MAX;
        for (int j = 0; j < i; ++j) {
            dp[i] = min(dp[i], 1+2*dp[j]+3*dp[i-1-j]+j*j*(i-1-j));
        }
        // cout << dp[i] << ' ';
    }
    cout << dp[2021] << endl;  // 2653631372
    return 0;
}

E: 大写

国赛遇上这种题应该挺难得吧。

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

int main()
{
    char c;
    while (cin >> c) cout << char((c | 0x20) ^ 0x20);
    return 0;
}

F: 123

这题公式其实蛮好推的。
只要知道 ∑ i = 1 n i = n ( n + 1 ) 2 ,   ∑ i = 1 n i 2 = n ( n + 1 ) ( 2 n + 1 ) 6 \sum_{i=1}^n i=\frac{n(n+1)}{2},\ \sum_{i=1}^n i^2=\frac{n(n+1)(2n+1)}{6} i=1ni=2n(n+1), i=1ni2=6n(n+1)(2n+1) 即可。

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

typedef long long LL;

LL calc(LL j) { return (j*(j+1)*(2*j+1)/6 + j*(j+1)/2)/2; }

LL solve(LL n)  // 计算 1...n 项和
{
    LL j = (sqrt(8*n+1)-1)/2;
    LL res = n - j*(j+1)/2;
    assert(res <= j);
    return calc(j) + res*(res+1)/2;
}

int main()
{
    LL T;
    cin >> T;
    while (T--) {
        LL l, r;
        cin >> l >> r;
        cout << solve(r) - solve(l-1) << endl;
    }
    return 0;
}

/*
3
1 1
1 3
5 8
*/

G: 异或变换(已补)

考场上没找到规律,只做了 40% 的子任务。

01 01 01 s = s 1 s 2 s 3 ⋯ s n s=s_1s_2s_3\cdots s_n s=s1s2s3sn .

在左边添加无数个 0 0 0 将其扩充为 ⋯ s − 1 s 0 s 1 s 2 ⋯ s n \cdots s_{-1}s_0s_1s_2\cdots s_n s1s0s1s2sn ,其中 s 0 = s − 1 = ⋯ = s − ∞ = 0 s_0=s_{-1}=\cdots =s_{-\infty}=0 s0=s1==s=0 ,容易证明 ∀ i ∈ ( − ∞ , n ] , s i ′ = s i ⊕ s i − 1 \forall i\in (-\infty,n],s_i' = s_i\oplus s_{i-1} i(,n],si=sisi1 .

为方便讨论,将 s s s 进行 t t t 次异或变换后的字符串记为 s ( t ) s^{(t)} s(t) .

则有公式

s i ( t )   = s i ( t − 1 ) ⊕ s i − 1 ( t − 1 ) = ( s i ( t − 2 ) ⊕ s i − 1 ( t − 2 ) ) ⊕ ( s i − 1 ( t − 2 ) ⊕ s i − 2 ( t − 2 ) )   = s i ( t − 2 ) ⊕ s i − 2 ( t − 2 ) = ( s i ( t − 4 ) ⊕ s i − 2 ( t − 4 ) ) ⊕ ( s i − 2 ( t − 4 ) ⊕ s i − 4 ( t − 4 ) )   = s i ( t − 4 ) ⊕ s i − 4 ( t − 4 ) = ⋯ \begin{aligned} s^{(t)}_i&\ {\color{red}=s^{(t-1)}_i\oplus s^{(t-1)}_{i-1}}\\ &=(s^{(t-2)}_i\oplus s^{(t-2)}_{i-1})\oplus(s^{(t-2)}_{i-1}\oplus s^{(t-2)}_{i-2})\ {\color{red}=s^{(t-2)}_i\oplus s^{(t-2)}_{i-2}}\\ &=(s^{(t-4)}_i\oplus s^{(t-4)}_{i-2})\oplus(s^{(t-4)}_{i-2}\oplus s^{(t-4)}_{i-4})\ {\color{red}=s^{(t-4)}_i\oplus s^{(t-4)}_{i-4}}\\ &=\cdots \end{aligned} si(t) =si(t1)si1(t1)=(si(t2)si1(t2))(si1(t2)si2(t2)) =si(t2)si2(t2)=(si(t4)si2(t4))(si2(t4)si4(t4)) =si(t4)si4(t4)=

由数学归纳法知,若 k k k 2 2 2 的幂且 k < t k < t k<t ,

s i ( t )   = s i ( t − k ) ⊕ s i − k ( t − k ) . s^{(t)}_i\ {\color{red}=s^{(t-k)}_i\oplus s^{(t-k)}_{i-k}}. si(t) =si(tk)sik(tk).

因此不断进行 2 2 2 的幂次变换,直至达到 t t t 次变换,即得结果。时间复杂度 O ( n log ⁡ t ) O(n\log t) O(nlogt) .

#include <iostream>
#include <string>
using namespace std;

int main()
{
    int n;
    long long t;
    scanf("%d%lld", &n, &t);
    string s(n, 0);
    scanf("%s", &s[0]);

    auto power_transform = [&] (long long k) {
        for (int i = n - 1; i >= 0; --i) {
            s[i] ^= (i - k >= 0 ? s[i - k] : '0') ^ '0';
        }
    };

    long long k = 1;
    while (t) {
        if (t & 1) power_transform(k);
        k <<= 1;
        t >>= 1;
    }

    printf("%s", &s[0]);

    return 0;
}

H: 二进制问题

这是很裸的数位 d p dp dp 吧。

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

typedef long long LL;

LL dp[100][2][100];  // 长度为i 首位为j 二进制中含k个1  dp代表满足条件的个数

void init()
{
    dp[1][0][0] = dp[1][1][1] = 1;
    for (int i = 2; i < 80; ++i) {
        for (int k = 0; k <= i; ++k) {
            dp[i][0][k] = dp[i-1][0][k] + dp[i-1][1][k];
            if (k > 0) dp[i][1][k] = dp[i-1][0][k-1] + dp[i-1][1][k-1];
            // cout << dp[i][0][k] << ',' << dp[i][1][k] << ' ';
        }
        // cout << endl;
    }
}

int digit[100];
int len = 0;
LL calc(LL n, LL k)  // 1..n中有多少个满足条件的数
{
    LL t = n;
    while (t) {
        digit[++len] = t&1; t >>= 1;
    }

    LL ans = 0;
    for (int i = len; i >= 1; --i) {
        if (i == 1) {
            ans += dp[i][ digit[i] ][k];
        }
        if (digit[i]) {
            ans += dp[i][0][k];
            --k;
        }
    }
    return ans;
}

int main()
{
    init();
    LL n, k;
    cin >> n >> k;
    cout << calc(n, k);
}

I: 反转括号序列(已补)

很明显的线段树特征,不过考场上还是没能想到怎么写,只做了 20% 的子任务。

思路一 线段树

记左括号为 1 1 1 ,右括号为 − 1 -1 1 ,可求得前缀和 sum ,需要维护的区间 [ L , R ] [L,R] [L,R] 内的信息:

  • lb : 区间左括号数
  • rb : 区间右括号数
  • mis : 前缀和的区间最小值 min ⁡ i = L R ( s u m i ) \min\limits_{i=L}^R(sum_i) i=LminR(sumi)
  • mas : 前缀和的区间最大值 max ⁡ i = L R ( s u m i ) \max\limits_{i=L}^R(sum_i) i=LmaxR(sumi)
  • mbs : 区间之前的前缀和 s u m L − 1 sum_{L-1} sumL1

两种操作:

  • 对于翻转操作,翻转区间 [ x , y ] [x,y] [x,y] 等价于依次翻转区间 [ x , n ] [x, n] [x,n] [ y + 1 , n ] [y+1,n] [y+1,n] .
  • 对于查询操作可以进行两次二分,第一次二分找出最大的 r r r 使得区间 [ x , r ] [x,r] [x,r]mis 不小于 mbs ,第二次二分找出最大的 y y y 使得区间 [ y , r ] [y,r] [y,r]mis 等于 mbs .

时间复杂度 O ( n + m log ⁡ 2 n ) O(n+m\log^2 n) O(n+mlog2n) .

#include <iostream>
#include <vector>
#include <string>
using namespace std;

const int inf = 1e9 + 7;
struct node {
    int lb, rb, mis, mas, mbs;
    bool lazy_rev;
    int lazy_add;
    node() : lb(0), rb(0)
            , mis(inf), mas(-inf)
            , lazy_rev(false), lazy_add(0) {}
};

using T = node;
class lazy_segtree {
    int n;
    vector<T> tree;
    void op(const T& a, const T& b, T& c) {
        c.lb = a.lb + b.lb;
        c.rb = a.rb + b.rb;
        c.mis = min(a.mis, b.mis);
        c.mas = max(a.mas, b.mas);
        c.mbs = a.mbs;
    }
    void push_up(int id) {
        op(tree[id << 1], tree[id << 1 | 1], tree[id]);
    }
    void rev_add(T &t, bool rev, int val) {
        if (rev) {
            swap(t.lb, t.rb);
            t.mis = 2 * t.mbs - t.mis;
            t.mas = 2 * t.mbs - t.mas;
            swap(t.mis, t.mas);
            t.lazy_rev ^= rev;
        }
        if (val) {
            t.mis += val;
            t.mas += val;
            t.mbs += val;
            t.lazy_add += val;
        }
    }
    void push_down(int L, int R, int id) {
        if (L == R) return;
        auto &lson = tree[id << 1];
        auto &rson = tree[id << 1 | 1];
        rev_add(lson, tree[id].lazy_rev, tree[id].lazy_add);
        rev_add(rson, tree[id].lazy_rev, tree[id].lazy_add + tree[id].lazy_rev * 2 * (lson.lb - lson.rb));
        tree[id].lazy_rev = false;
        tree[id].lazy_add = 0;
    }
    void build(vector<T> &a, int L, int R, int id) {
        if (L == R) {
            tree[id] = a[L - 1];
            return;
        }
        int m = L + R >> 1;
        build(a, L, m, id << 1);
        build(a, m + 1, R, id << 1 | 1);
        push_up(id);
    }
    int rev(int x, int y, int val, int L, int R, int id) {
        if (x <= L && R <= y) {
            rev_add(tree[id], true, val);
            return 2 * (tree[id].lb - tree[id].rb);
        }
        push_down(L, R, id);
        int m = L + R >> 1;
        int t = 0;
        if (x <= m) t = rev(x, y, val, L, m, id << 1);
        if (y > m) t += rev(x, y, val + t, m + 1, R, id << 1 | 1);
        push_up(id);
        return t;
    }
    T query(int x, int y, int L, int R, int id) {
        push_down(L, R, id);
        if (x <= L && R <= y) {
            return tree[id];
        }
        int m = L + R >> 1;
        T res;
        if (x <= m) op(res, query(x, y, L, m, id << 1), res);
        if (y > m) op(res, query(x, y, m + 1, R, id << 1 | 1), res);
        return res;
    }
public:
    lazy_segtree(vector<T> &v) : n(v.size()), tree(n << 2) {
        build(v, 1, n, 1);
    }
    void rev(int x, int y) {
        rev(x, n, 0, 1, n, 1);
        if (y + 1 <= n) rev(y + 1, n, 0, 1, n, 1);
    }
    int query(int l) {
        T t = query(l ,l, 1, n, 1);
        if (t.rb) return 0;
        int mis = t.mis - 1;
        int L = l, R = n;
        while (L < R) {
            int m = L + R + 1 >> 1;
            if (query(l, m, 1, n, 1).mis >= mis) L = m;
            else R = m - 1;
        }
        int r = R;
        t = query(l ,r, 1, n, 1);
        if (t.mis > mis) return 0;
        L = l;
        while (L < R) {
            int m = L + R + 1 >> 1;
            if (query(m, r, 1, n, 1).mis == mis) L = m;
            else R = m - 1;
        }
        return L;
    }
};

int main()
{
    int n, m;
    scanf("%d%d", &n, &m);
    string s(n, 0);
    scanf("%s", &s[0]);
    vector<node> v(n);
    int t = 0;
    for (int i = 0; i < n; ++i) {
        v[i].lb = (s[i] == '(');
        v[i].rb = (s[i] == ')');
        v[i].mbs = t;
        t += v[i].lb - v[i].rb;
        v[i].mis = v[i].mas = t;
    }
    lazy_segtree st(v);
    while (m--) {
        int t, x, y;
        scanf("%d%d", &t, &x);
        if (t == 1) {
            scanf("%d", &y);
            st.rev(x, y);
        }
        else {
            printf("%d\n", st.query(x));
        }
    }
    return 0;
}

思路二 分块

记录每一个块中翻转和不反转情况下多余的左括号与右括号的数目,以及翻转标志。时间复杂度 O ( m n ) O(m\sqrt n) O(mn ) .

#include <iostream>
#include <vector>
#include <string>
#include <cmath>
using namespace std;

struct Block {
    bool rev;
    int nl[2], nr[2];
    Block() : rev(false) {}
};

int main()
{
    int n, m;
    scanf("%d%d", &n, &m);
    string s(n, 0);
    scanf("%s", &s[0]);

    int step = sqrt(n) + 1, sz = (n + step - 1) / step;
    vector<Block> blocks(sz);

    auto update = [&] (int i) {
        blocks[i].nl[0] = 0;
        blocks[i].nl[1] = 0;
        blocks[i].nr[0] = 0;
        blocks[i].nr[1] = 0;
        int l = i * step, r = min(n, l + step);
        for (int j = l; j < r; ++j) {
            if (s[j] == '(') ++blocks[i].nl[0];
            else if (blocks[i].nl[0]) --blocks[i].nl[0];
            else ++blocks[i].nr[0];
        }

        for (int j = l; j < r; ++j) {
            if (s[j] == ')') ++blocks[i].nl[1];
            else if (blocks[i].nl[1]) --blocks[i].nl[1];
            else ++blocks[i].nr[1];
        }
    };

    for (int i = 0; i < sz; ++i) {
        update(i);
    }

    while (m--) {
        int t, x, y;
        scanf("%d%d", &t, &x);
        --x;
        if (t == 1) {
            scanf("%d", &y);
            --y;
            int  i = x / step, j = y / step;
            int l = x, r = min(n, (i + 1) * step);
            r = min(r, y + 1);
            for (int j = l; j < r; ++j) {
                s[j] = '(' - s[j] + ')';
            }
            update(i);
            while (++i < j) {
                blocks[i].rev ^= 1;
            }
            if (i == j) {
                l = i * step, r = y + 1;
                for (int j = l; j < r; ++j) {
                    s[j] = '(' - s[j] + ')';
                }
                update(i);
            }
        }
        else {
            int ans = 0;
            int cnt = 0;
            int i = x / step;
            int l = x, r = min(n, (i + 1) * step);
            for (int j = x; j < r; ++j) {
                cnt += ((s[j] == '(') ^ (blocks[i].rev)) ? 1 : -1;
                if (cnt < 0) break;
                if (cnt == 0) ans = j + 1;
            }
            if (cnt >= 0) {
                int t = i;
                int p = i, pc;
                while (++i < sz) {
                    if (blocks[i].nr[blocks[i].rev] < cnt) {
                        cnt += blocks[i].nl[blocks[i].rev] - blocks[i].nr[blocks[i].rev];
                        continue;
                    }
                    if (blocks[i].nr[blocks[i].rev] >= cnt) p = i, pc = cnt;
                    if (blocks[i].nr[blocks[i].rev] > cnt) break;
                    cnt += blocks[i].nl[blocks[i].rev] - blocks[i].nr[blocks[i].rev];
                }
                if (p > t) {
                    int l = p * step, r = min(n, l + step);
                    cnt = pc;
                    if (cnt == 0) ans = l;
                    for (int j = l; j < r; ++j) {
                        cnt += ((s[j] == '(') ^ (blocks[p].rev)) ? 1 : -1;
                        if (cnt < 0) break;
                        if (cnt == 0) ans = j + 1;
                    }
                }
            }
            printf("%d\n", ans);
        }
    }
    return 0;
}

J: 异或三角形(已补)

考场上没想到思路,只做了 20% 的子任务。

由题意知

  • a ⊕ b ⊕ c = 0 ⇔ c = a ⊕ b a\oplus b\oplus c = 0 \Leftrightarrow c = a\oplus b abc=0c=ab ;
  • a , b , c a,b,c a,b,c 能构成三角形 ⇔ ∣ a − b ∣ < c < a + b \Leftrightarrow |a-b|\lt c \lt a+b ab<c<a+b .

又因为当 a > b a > b a>b 时有

  • a − b ≤ a ⊕ b ≤ a + b a-b \le a\oplus b \le a + b ababa+b .
  • a − b = a ⊕ b a-b = a\oplus b ab=ab 当且仅当 a ∣ b = a a | b = a ab=a .
  • a ⊕ b = ≤ a + b a\oplus b = \le a + b ab=≤a+b 当且仅当 a & b = 0 a \& b = 0 a&b=0 .

记 $a=a_{29}a_{28}a_{27}\cdots a_1a_{0}, $ b = b 29 b 28 b 27 ⋯ b 1 b 0 b=b_{29}b_{28}b_{27}\cdots b_1b_{0} b=b29b28b27b1b0 ,其中 a i , b i a_i,b_i ai,bi 分别代表 a , b a,b a,b 在第 i i i 位的二进制数.

故原题等价于求出有多少组 a , b a,b a,b 满足

  • 1 ≤ a , b , a ⊕ b ≤ n 1 \le a,b,a\oplus b\le n 1a,b,abn ;
  • ∃ i , ( a i , b i ) = ( 0 , 1 ) \exists i,(a_i,b_i)=(0,1) i,(ai,bi)=(0,1) ;
  • ∃ i , ( a i , b i ) = ( 1 , 1 ) \exists i,(a_i,b_i)=(1,1) i,(ai,bi)=(1,1) ;
  • ∃ i , ( a i , b i ) = ( 1 , 0 ) \exists i,(a_i,b_i)=(1,0) i,(ai,bi)=(1,0) .

因此不妨设 a > b a > b a>b ,记忆化搜索求出数目后乘以 2 2 2 即得答案。

由于 dfs 时 i i i 的状态只从 i − 1 i-1 i1 的状态转移过来,可将记忆化搜索计算数组 f f f 的过程视为动态规划。时间复杂度 O ( T log ⁡ n ) O(T\log n) O(Tlogn) .

#include <iostream>
#include <cstring>

using namespace std;

long long f[2][1 << 3][30];  // f[cond][s][i] 代表 cond 约束下,当 a, b 的高位(比第 i 位更高的位)状态为 s 时合法的 (a,b) 对数目。

long long dfs(int i, int weight, int high_a, int high_b, int n, int s) {
    int high_c = high_a ^ high_b;
    if (high_a > n || high_b > high_a || high_c > n) return 0;
    if (weight == 0) return s == 7;

    int mask = weight * 2 - 1;
    bool cond = high_a + mask <= n && high_c + mask <= n;

    if (f[cond][s][i] != -1) return f[cond][s][i];

    long long ans = 0;
    ans += dfs(i - 1, weight >> 1, high_a, high_b, n, s);
    ans += dfs(i - 1, weight >> 1, high_a, high_b + weight, n, s | 1);
    ans += dfs(i - 1, weight >> 1, high_a + weight, high_b, n, s | 4);
    ans += dfs(i - 1, weight >> 1, high_a + weight, high_b + weight, n, s | 2);
    return f[cond][s][i] = ans;
}

int main()
{
    int T;
    scanf("%d", &T);
    while (T--) {
        memset(f, -1, sizeof(f));
        int n;
        scanf("%d", &n);
        printf("%lld\n", dfs(29, 1 << 29, 0, 0, n, 0) * 2);
    }
    return 0;
}

#if 0
2
6
114514
#endif

总结

这是我本科第一次参加蓝桥杯,我想大概也是最后一次了。

感觉自己还是一如既往地菜,希望以后能不忘初心,继续学习。

如果研究生阶段还有机会参加蓝桥杯的话,希望到时候也可以向那些大佬一样骄傲地说出蓝桥杯 “有手就行、点击就送” 吧。

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值