第十四届蓝桥杯大赛软件赛省赛(C++研究生组)

工作时长

将输入时间换算成距离 2022-01-01 00:00:00 的秒数,排序后成对计算即可.

#include <iostream>
#include <cstdio>
#include <cassert>
#include <vector>
#include <algorithm>
using namespace std;

int main()
{
    freopen("records.txt", "r", stdin);
    int y, mo, d, h, mi, s;
    vector<int> days = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
    vector<int> v;
    while (scanf("%d-%d-%d %d:%d:%d", &y, &mo, &d, &h, &mi, &s) == 6) {
//        printf("%d-%d-%d %d:%d:%d\n", y, mo, d, h, mi, s);
        assert(y == 2022);
        int t = 0;
        for (int i = 1; i < mo; ++i) {
            t += days[i];
        }
        t += d;
        t = t * 24 + h;
        t = t * 60 + mi;
        t = t * 60 + s;
        v.push_back(t);
    }
    assert(v.size() % 2 == 0);
    sort(v.begin(), v.end());
    int ans = 0;
    for (int i = 0; i < v.size(); i += 2) ans += v[i + 1] - v[i];
    cout << ans;  // 5101913
    return 0;
}

#if 0
2022-01-01 12:00:05
2022-01-02 00:20:05
2022-01-01 07:58:02
2022-01-01 16:01:35
#endif

与或异或

10 10 10 个门电路,每个门电路 3 3 3 种状态,总共 3 10 = 59049 3^{10}=59049 310=59049 种状态. 枚举即可.

#include <iostream>
using namespace std;

int main()
{
    const int n = 5;
    int arr[n][n];
    arr[0][0] = 1; arr[0][1] = 0; arr[0][2] = 1; arr[0][3] = 0; arr[0][4] = 1;
    int tot = 59049;
    int ans = 0;
    for (int t = 0; t < tot; ++t) {
        int s = t;
        for (int i = 1; i < n; ++i) {
            for (int j = 0; j < n - i; ++j) {
                int op = s % 3;
                s /= 3;
                switch (op) {
                    case 0: arr[i][j] = arr[i - 1][j] & arr[i - 1][j + 1]; break;
                    case 1: arr[i][j] = arr[i - 1][j] | arr[i - 1][j + 1]; break;
                    case 2: arr[i][j] = arr[i - 1][j] ^ arr[i - 1][j + 1]; break;
                }
            }
        }
        ans += (arr[n - 1][0] == 1);
    }
    cout << ans;  // 30528
    return 0;
}

翻转

注意到:

  • 最左和最右的棋子无法翻转.
  • 一颗棋子在翻转后无法再翻转回来,因此每个位置最多翻转一次.
  • 一颗棋子翻转后,与它相邻的棋子无法进行翻转.
  • S S S T T T 棋子不同的位置必须要翻转.

因此,如果 S S S 通过若干次翻转可以得到 T T T ,需要翻转的位置一定不在两端且互不相邻,且翻转的次序可以任意.

贪心地一次遍历即可.

#include <cstdio>

const int N = 1e6 + 2;
char S[N];
char T[N];

int main()
{
    int D;
    scanf("%d", &D);
    while (D--) {
        scanf("%s%s", T + 1, S + 1);
        bool ok = true;
        int cnt = 0;
        for (int i = 1; S[i]; ++i) {
            if (S[i] == T[i]) continue;
            if (i > 1 && S[i + 1])  // 如果不在两端
            if (S[i] != S[i - 1] && S[i] != S[i + 1]) {
                S[i] = '1' - S[i] + '0';
                ++cnt;
                continue;
            }
            ok = false;
            break;
        }
        int ans = ok ? cnt : -1;
        printf("%d\n", ans);
    }
}

阶乘的和

A 1 , A 2 , ⋯   , A n A_1,A_2,\cdots,A_n A1,A2,,An 排序得到 a 1 , a 2 , ⋯   , a n a_1,a_2,\cdots,a_n a1,a2,,an .

注意到:

  • ∑ i = 1 n ( A i ! ) = ∑ i = 1 n ( a i ! ) \sum_{i=1}^n(A_i!)=\sum_{i=1}^n(a_i!) i=1n(Ai!)=i=1n(ai!) .
  • ( a 1 ! ) ∣ ∑ i = 1 n ( a i ! ) (a_1!)|\sum_{i=1}^n(a_i!) (a1!)i=1n(ai!) , ( a p ! ) ∣ ∑ i = p n ( a i ! ) (a_p!)|\sum_{i=p}^n(a_i!) (ap!)i=pn(ai!) .
  • m ! ∣ ∑ i = 1 n ( a i ! ) m!|\sum_{i=1}^n(a_i!) m!i=1n(ai!) , 则 ( m − 1 ) ! ∣ ∑ i = 1 n ( a i ! ) (m-1)!|\sum_{i=1}^n(a_i!) (m1)!i=1n(ai!).

因此,若 M M M 是满足条件的最大的 m m m ,即 M ! ∣ ∑ i = 1 n ( a i ! ) M!|\sum_{i=1}^n(a_i!) M!i=1n(ai!) ,则 ∑ i = 1 n ( a i ! ) m o d    ( M + 1 ) ! = k × M ! \sum_{i=1}^n(a_i!) \mod (M+1)!=k\times M! i=1n(ai!)mod(M+1)!=k×M! ,其中 k ∈ { 1 , 2 , ⋯   , M } k\in\{1,2,\cdots,M\} k{1,2,,M} .

初始化 m = a 1 m=a_1 m=a1 不断假设答案为 m m m, 求解 ∑ i = 1 n ( a i ! ) \sum_{i=1}^n(a_i!) i=1n(ai!) 取模 ( m + 1 ) ! (m+1)! (m+1)! 的余数 k × m ! k\times m! k×m! . 当 1 ≤ k ≤ m 1\le k \le m 1km 时,算法结束,此时的 m m m 即为结果.

时间复杂度 O ( n ) O(n) O(n) .

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

int main()
{
    int n;
    scanf("%d", &n);
    vector<int> a(n);
    for (auto &p : a) scanf("%d", &p);
    sort(a.begin(), a.end());
    int k = 0, m = a[0];
    for (int i = 0; i < n; ++i) {
        if (m == a[i]) {
            ++k;
            continue;
        }
        while (k > m && k % (m + 1) == 0) {
            ++m;
            k /= m;
            if (m == a[i]) {
                ++k;
                break;
            }
        }
    }
    while (k > m && k % (m + 1) == 0) {
        ++m;
        k /= m;
    }
    printf("%d", m);
    return 0;
}

公因数匹配

A i , A j A_i,A_j Ai,Aj 存在大于 1 1 1 的公因数,等价于 A i , A j A_i,A_j Ai,Aj 均含有某个质因子.

p o s x pos_x posx 为质因子 x x x 出现的最早位置.

对于每一个 j j j 求出最小的 i i i 并更新答案即可.

时间复杂度 O ( n A m a x ) O(n\sqrt{A_{max}}) O(nAmax ) . 预处理 1000 1000 1000 以内的素数表可将复杂度降为 ( n log ⁡ A m a x ) (n\log{A_{max}}) (nlogAmax) .

本题存在没有答案的情况(如当 n = 1 n=1 n=1 时),但题目未作说明,不妨假设输入保证有解.

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

const int M = 1e6 + 1;

int pos[M];  // pos[x] 代表质因数 x 最早出现的位置,初始化为 0

int main()
{
    int n;
    scanf("%d", &n);
    int ansi = n + 1, ansj = -1;
    for (int i = 1; i <= n; ++i) {
        int x;
        scanf("%d", &x);
        int l = n + 1;
        for (int t = 2; t <= x / t; ++t) {
            if (x % t) continue;
            do x /= t; while (x % t == 0);
            if (pos[t]) l = min(l, pos[t]);
            else pos[t] = i;
        }
        if (x > 1) {
            if (pos[x]) l = min(l, pos[x]);
            else pos[x] = i;
        }
        if (l < ansi) ansi = l, ansj = i;
    }
    printf("%d %d", ansi, ansj);
    return 0;
}

#if 0
5
5 3 2 6 9
#endif

奇怪的数

题面没有说明数位是从左往右数还是从右往左数(二者在允许出现前导 0 0 0 的前提下没有区别),这份题解默认从左往右数.

f i , a , b , c , d f_{i,a,b,c,d} fi,a,b,c,d 代表长度为 i i i 且后四位为 a b c d abcd abcd 的奇怪的数的个数,其中 i ≥ 5 i\ge 5 i5. 则

f i + 1 , a , b , c , d = ∑ e ≤ m − a − b − c − d f i , e , a , b , c f_{i+1,a,b,c,d}=\sum_{e \le m-a-b-c-d}f_{i,e,a,b,c} fi+1,a,b,c,d=emabcdfi,e,a,b,c

初始化暴力算出 f 5 , a , b , c , d f_{5,a,b,c,d} f5,a,b,c,d 即可.

时间复杂度 O ( n ⋅ D 5 ) O(n\cdot D^5) O(nD5) ,其中 D D D 代表每一位可能的取值个数. 用另一个数组 s s s 维护 ∑ e f i , e , a , b , c \sum_{e}f_{i,e,a,b,c} efi,e,a,b,c 的值可将复杂度降为 O ( n ⋅ D 4 ) O(n\cdot D^4) O(nD4) . 计算量在 1 0 8 10^8 108 级别,不过开启 O 2 O2 O2 优化之后可以轻松在 1 s 1s 1s 内计算出 n = 200000 , m = 50 n=200000,m=50 n=200000,m=50 的结果. 不过这个复杂度还是挺极限的,如果存在更好的做法,望告知!

由于 f i f_i fi 的状态仅取决于 f i − 1 f_{i - 1} fi1 的状态,因此可采用滚动数组,空间复杂度 O ( D 4 ) O(D^4) O(D4) .

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

const int mod = 998244353;
const int M = 12;

int f[2][M][M][M][M];
int s[2][M][M][M][M];

int main()
{
    int n, m;
    scanf("%d%d", &n, &m);
    int t = 0;
    for (int a = 0; a < 10; a += 2) {
        for (int b = 1; b < 10; b += 2) {
            for (int c = 0; c < 10; c += 2) {
                for (int d = 1; d < 10; d += 2) {
                    int &val = f[t][a][b][c][d];
                    for (int e = 1; e < 10 && e <= m - a - b - c - d; e += 2) ++val;
                    s[t][a + 2][b][c][d] = s[t][a][b][c][d] + val;

                }
            }
        }
    }
    for (int i = 6; i <= n; ++i) {
        t ^= 1;
        for (int a = (i - 3) & 1; a < 10; a += 2) {
            for (int b = (i - 2) & 1; b < 10; b += 2) {
                for (int c = (i - 1) & 1; c < 10; c += 2) {
                    for (int d = i & 1; d < 10; d += 2) {
                        int &val = f[t][a][b][c][d];
                        val = 0;
                        if (a + b + c + d <= m) {
                            int e = min(9, m - a - b - c - d);
                            if ((i - 4) & 1) { if (!(e & 1)) --e; }
                            else { if (e & 1) --e; }
                            val = s[t ^ 1][e + 2][a][b][c];
                        }
                        s[t][a + 2][b][c][d] = (s[t][a][b][c][d] + val) % mod;
                    }
                }
            }
        }
    }
    int ans = 0;
    for (int a = (n - 3) & 1; a < 10; a += 2) {
        for (int b = (n - 2) & 1; b < 10; b += 2) {
            for (int c = (n - 1) & 1; c < 10; c += 2) {
                for (int d = n & 1; d < 10; d += 2) {
                    ans += f[t][a][b][c][d];
                    if (ans >= mod) ans -= mod;
                }
            }
        }
    }

    printf("%d", ans);
    return 0;
}

#if 0
5 5 => 6
100000 50 => 976293084
200000 50 => 670295496
#endif

太阳

让一条来自太阳的射线从 x x x 轴负向开始逆时针旋转,扫描过程中记录被照到的线段即可.

时间复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn) .

#include <iostream>
#include <vector>
#include <algorithm>
#include <set>
using namespace std;

struct node {
    int x, y, i;
    node(int x, int y, int i) : x(x), y(y), i(i) {}
    bool operator< (const node &t) const
    {
        long long a = 1LL * t.y * x;
        long long b = 1LL * y * t.x;
        return a < b;
    }
    bool operator== (const node &t) const {
        return 1LL * t.y * x == 1LL * y * t.x;
    }
};

int main()
{
    int n, X, Y;
    scanf("%d%d%d", &n, &X, &Y);
    vector<node> v;
    vector<bool> vis(n + 1);
    for (int i = 1; i <= n; ++i) {
        int x, y, l;
        scanf("%d%d%d", &x, &y, &l);
        v.emplace_back(x - X, Y - y, i);
        v.emplace_back(x + l - X, Y - y, -i);
    }
    sort(v.begin(), v.end());
    typedef pair<int,int> pii;
    set<pii> st;
    auto last = node(-1, 1, 0);
    for (auto &t : v) {
        if (!(t == last)) {
            if (!st.empty()) {
                vis[st.begin()->second] = true;
            }
            last = t;
        }
        if (t.i > 0) st.emplace(t.y, t.i);
        else st.erase(make_pair(t.y, -t.i));
    }
    int ans = 0;
    for (bool t : vis) ans += t;
    printf("%d", ans);
    return 0;
}

子树的大小

i i i 层的结点个数是 m i m^{i} mi i i i 0 0 0 开始.

因此第 h h h 层最左端结点的编号为 1 + ∑ i = 0 h − 1 m i 1+\sum_{i=0}^{h-1}m^i 1+i=0h1mi .

求出 k k k 号结点和 n n n 号所在层数及其左边的结点个数后分类讨论即可.

k k k n n n 在同一层,答案为 1 1 1 .

否则答案为 ∑ i = 0 h n − 1 − h k m i + max ⁡ ( 0 , min ⁡ ( m h n − h k , l n + 1 − l k × m h n − h k ) ) \sum_{i=0}^{h_n-1-h_k}m^i+\max(0,\min(m^{h_n-h_k},l_n + 1 - l_k \times m^{h_n-h_k})) i=0hn1hkmi+max(0,min(mhnhk,ln+1lk×mhnhk)) ,其中 h ⋅ h_\cdot h 代表结点所在层数, l ⋅ l_\cdot l 代表当前层该结点左边的结点个数.

时间复杂度 O ( T log ⁡ n ) O(T\log n) O(Tlogn) .

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

void get_info(int m, int k, int &h, int &l) {
    long long cnt = 0;
    long long num = 1;
    h = 0;
    while (cnt + num < k) {
        cnt += num;
        ++h;
        num *= m;
    }
    l = k - cnt - 1;
}

int main()
{
    int T;
    scanf("%d", &T);
    while (T--) {
        int n, m, k;
        scanf("%d%d%d", &n, &m, &k);
        int hk, lk;
        get_info(m, k, hk, lk);
        int hn, ln;
        get_info(m, n, hn, ln);
        if (hk == hn) {
            puts("1");
            continue;
        }
        int ans = 0;
        long long t = 1;
        for (int i = 0; i < hn - hk; ++i) {
            ans += t;
            t *= m;
        }
        ans += max(0LL, min(t, ln + 1 - lk * t));
        printf("%d\n", ans);
    }
    return 0;
}
#if 0
3
1 2 1
11 3 4
74 5 3
#endif

高塔

小蓝在第 n n n 回合后结束游戏的方案数为

∑ c 1 + c 2 + ⋯ + c n ≤ m ∏ j = 1 n A j c j = ( ∑ c 1 + c 2 + ⋯ + c n ≤ m ∏ j = 1 n c j ) ⋅ ∏ j = 1 n A j = T n , m ⋅ ∏ j = 1 n A j \begin{aligned} \sum\limits_{c_1+c_2+\cdots +c_n\le m}\prod\limits_{j=1}^nA_jc_j &= \left(\sum\limits_{c_1+c_2+\cdots +c_n\le m}\prod\limits_{j=1}^nc_j\right)\cdot\prod\limits_{j=1}^nA_j \\ &= T_{n, m}\cdot \prod\limits_{j=1}^nA_j \end{aligned} c1+c2++cnmj=1nAjcj=(c1+c2++cnmj=1ncj)j=1nAj=Tn,mj=1nAj

小蓝在第 i ( 1 ≤ i < n ) i(1\le i\lt n) i(1i<n) 回合后耗尽能量的方案数为

∑ c 1 + c 2 + ⋯ + c i = m ∏ j = 1 i A j c j = ( ∑ c 1 + c 2 + ⋯ + c i = m ∏ j = 1 i c j ) ⋅ ∏ j = 1 i A j = ( T i , m − T i , m − 1 ) ⋅ ∏ j = 1 i A j \begin{aligned} \sum\limits_{c_1+c_2+\cdots +c_i=m}\prod\limits_{j=1}^iA_jc_j &= \left(\sum\limits_{c_1+c_2+\cdots +c_i=m}\prod\limits_{j=1}^ic_j\right)\cdot\prod\limits_{j=1}^iA_j\\ &= (T_{i,m}-T_{i, m-1})\cdot\prod\limits_{j=1}^iA_j \end{aligned} c1+c2++ci=mj=1iAjcj=(c1+c2++ci=mj=1icj)j=1iAj=(Ti,mTi,m1)j=1iAj

其中 T i , j = ∑ c 1 + c 2 + ⋯ + c i ≤ j ∏ k = 1 i c k = C i + j 2 i T_{i,j}=\sum\limits_{c_1+c_2+\cdots +c_i\le j}\prod\limits_{k=1}^ic_k=\mathrm C_{i+j}^{2i} Ti,j=c1+c2++cijk=1ick=Ci+j2i.

∑ c 1 + c 2 + ⋯ + c i ≤ j ∏ k = 1 i c k = C i + j 2 i \sum\limits_{c_1+c_2+\cdots +c_i\le j}\prod\limits_{k=1}^ic_k=\mathrm C_{i+j}^{2i} c1+c2++cijk=1ick=Ci+j2i 的证明参见 https://math.stackexchange.com/questions/4678677/how-can-i-prove-this-formula-sum-a-1a-2-cdots-a-m-le-n-prod-i-1ma-i .

则总的方案数为

( T n , m ⋅ ∏ j = 1 n A j ) + ( ∑ i = 1 n − 1 ( T i , m − T i , m − 1 ) ⋅ ∏ j = 1 i A j ) \left(T_{n, m}\cdot \prod\limits_{j=1}^nA_j\right)+\left(\sum_{i=1}^{n-1}(T_{i,m}-T_{i, m-1})\cdot\prod\limits_{j=1}^iA_j\right) (Tn,mj=1nAj)+(i=1n1(Ti,mTi,m1)j=1iAj)

时间复杂度 O ( n ⋅ α ) O(n\cdot \alpha) O(nα) ,其中 α \alpha α 代表求逆元的均摊复杂度.

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

int inv(int a, int p) {
    int b = p - 2;
    int ans = 1;
    while (b) {
        if (b & 1) ans = (long long) ans * a % p;
        a = (long long) a * a % p;
        b >>= 1;
    }
    return ans;
}

int main()
{
    const int mod = 998244353;
    int n;
    long long m;
    cin >> n >> m;
    vector<int> A(n + 1);
    for (int i = 1; i <= n; ++i) scanf("%d", &A[i]);
    int ans = 0;
    int prod = 1;
    int Tm = 1, Tm_1 = 1;
    int m_1 = (m - 1) % mod;
    m %= mod;
    for (int i = 1; i <= n; ++i) {
        Tm = (long long) Tm * (m + i) % mod * (m - i + 1) % mod * inv(2 * i, mod) % mod * inv(2 * i - 1, mod) % mod;
        Tm_1 = (long long) Tm_1 * (m_1 + i) % mod * (m_1 - i + 1) % mod * inv(2 * i, mod) % mod * inv(2 * i - 1, mod) % mod;
        prod = 1LL * prod * A[i] % mod;
        if (i < n) ans = (ans + 1LL * (Tm - Tm_1 + mod) * prod % mod) % mod;
        else ans = (ans + 1LL * Tm * prod % mod) % mod;
    }
    cout << ans;
    return 0;
}
#if 0
9 15
3 2 5 7 1 4 6 8 3
#endif

反异或 01 串

逆向思维:给定一个 01 01 01 T T T ,每步操作可以将最左端或最右端的 0 0 0 1 1 1 移除. 也可以对整个串进行一次逆向的反异或操作. 问最少需要移除多少个 1 1 1 .

由反异或操作的定义知

  • 逆向的反异或操作要一定作用在 T T T 的某个子串 t t t 上.
  • t t t 必须是回文串,且最中间的元素(如果长度为奇数)必须是 0 0 0 .

因此,不难发现

  • 若不进行反异或操作,需要移除 O N E T \mathrm{ONE}_T ONET 1 1 1.
  • 若进行一次反异或操作,需要移除 O N E T − O N E t / 2 \mathrm{ONE}_T-\mathrm{ONE}_t/2 ONETONEt/2 1 1 1.

其中 O N E ⋅ \mathrm{ONE}_\cdot ONE 为串 ⋅ \cdot 1 1 1 的个数.

为避免考虑回文子串的奇偶性,可在串 T T T ∣ T ∣ − 1 |T|-1 T1 个间隙中插入一个 0 0 0 得到新的串 T ′ T' T ,不难发现, T T T 中所有的回文串都对应 T ′ T' T 中一个长度为奇数的回文串.

f i f_i fi 代表以 i i i 为中心的最长回文串的右端点. o n e i one_i onei 代表以 i i i 为中心的最长回文串中 1 1 1 的数目. 通过 M a n a c h e r Manacher Manacher 算法可求得其值.

时间复杂度 O ( ∣ T ′ ∣ ) = O ( ∣ T ∣ ) O(|T'|)=O(|T|) O(T)=O(T) .

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

const int N = 2e6;
char T[N];
int f[N];
int sum[N];

int main()
{
    scanf("%s", T);
    int n = strlen(T);
    int ones_T = (T[0] == '1');
    for (int i = n - 1; i > 0; --i) {
        ones_T += (T[i] == '1');
        T[i * 2] = T[i];
        T[i * 2 - 1] = '0';
    }
    n += n - 1;
    for (int i = 0; i < n; ++i) sum[i + 1] = sum[i] + (T[i] == '1');
    int ans = ones_T;
    f[0] = 0;
    int p = 0;
    for (int i = 1; i < n; ++i) {
        f[i] = i;
        if (f[p] > i) f[i] += min(f[p] - i, f[2 * p - i] - (2 * p - i));
        if (f[i] >= f[p]) {
            p = i;
            while (2 * p >= f[p] + 1 && T[f[p] + 1] == T[2 * p - (f[p] + 1)]) ++f[p];
        }
        if (T[i] == '0') {
            int ones_t = (sum[f[i] + 1] - sum[i]) * 2;
            ans = min(ans, ones_T - ones_t / 2);
        }
    }
    printf("%d", ans);
    return 0;
}
  • 10
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值