1. 程式人生 > 其它 >AtCoder ABC 242 題解

AtCoder ABC 242 題解

AtCoder ABC 242 題解

A T-shirt

排名前 \(A\) 可得 T-shirt

排名 \([A+1,B]\) 中隨機選 \(C\) 個得 T-shirt

給出排名 \(X\) ,求得到 T-shirt 的概率

  • 一個 if 即可

B Minimize Ordering

給一個字串 \(S\) ,求 \(S\) 排列出的字典序最小的字串

  • 一個排序

C 1111gal password

\(n\) ,求出滿足條件的 \(n\) 位數 \(X\) 個數 \(\;mod\;998244353\)

  • \(\forall i\in[1,n],\quad1\le X_i\le 9\)
  • \(\forall i\in[1,n),\quad|x_i-x_{i+1}\le 1|\)
  • \(f_{i,j}\) 表示到第 \(i\) 位且為 \(j\) 的方案數,轉移顯然,\(O(10n)\)

D ABC Transform

A,B,C 組成的字串 \(S\) ,一次變換,A -> BC, B -> CA, C -> AB

\(S^{(i)}\) 表示 \(S\) 經過 \(i\) 次變換的字串

\(Q\) 次詢問,第 \(i\) 次詢問 \(S^{(t_i)}\) 的第 \(k_i\) 個字元

\(Q\le 10^5\)\(t,k\le 10^{18}\)


相當於從滿二叉樹的某一個點找到根。

就是一個 while 往上跳,每次其實就是字元往後 1 或 2。開一個變數記錄即可,\(O(Q\log k)\)

#include <bits/stdc++.h>
using namespace std;
typedef unsigned long long uLL;
typedef long double LD;
typedef long long LL;
typedef double db;
const int N = 100005;
int n, Ti;
char x[N];
int main() {
    scanf("%s%d", x + 1, &Ti);
    n = strlen(x + 1);
    for (LL a, b, c; Ti--; ) {
        scanf("%lld%lld", &a, &b);
        c = 0;
        while (a && b > 1) {
            if (!(b & 1)) ++c;
            ++c;
            b = b + 1 >> 1, a--;
        }
        c += a;
        printf("%c\n", char((x[b] - 'A' + c) % 3 + 'A'));
    }
}

E (∀x∀)

\(T\) 組資料,每次給長度為 \(n\) 的字串 \(S\)

求長度為 \(n\) ,字典序小於 \(S\) 的迴文串 \(X\) 的數量 \(\;mod\;998244353\)

\(T\le 250000, \sum n\le 10^6\)


迴文只用構造一半,列舉前半部分 \(i\) ,若 \(X_i<S_i\) 則區間 \([i+1,mid]\) 可以任意填

最後看 \(X,S\) 前半部分相同是否滿足條件,\(O(Tn)\)

#include <bits/stdc++.h>
using namespace std;
typedef unsigned long long uLL;
typedef long double LD;
typedef long long LL;
typedef double db;
const int N = 1000005;
const LL P = 998244353;
int Ti, n, mi;
char a[N];
LL ans, fac[N];
inline int chk() {
    for (int i = mi; i; i--) {
        if (a[i] > a[n - i + 1]) return 0;
        if (a[i] < a[n - i + 1]) return 1;
    }
    return 1;
}
int main() {
    fac[0] = 1;
    for (int i = 1; i <= 1000000; i++) fac[i] = fac[i - 1] * 26 % P;
    scanf("%d", &Ti);
    while (Ti--) {
        scanf("%d", &n);
        for (int i = 1; i <= n; i++) {
            a[i] = getchar();
            while (a[i] < 'A' || a[i] > 'Z') a[i] = getchar();
        }
        mi = n + 1 >> 1, ans = chk();
        for (int i = 1; i <= mi; i++) {
            (ans += 1ll * (a[i] - 'A') * fac[mi - i] % P) %= P;
        }
        printf("%lld\n", ans);
    }
}

F Black and White Rooks

\(B\) 個黑棋,\(W\) 個白棋,放在 \(n\times m\) 的棋盤上,

一個棋子在 \((i,j)\) 則第 \(i\) 行第 \(j\) 列都不能有與其顏色不同的棋子

相同顏色的棋沒有區別

求放完所有棋子的方案數 \(\;mod\; 998244353\)

\(n,m\le 50,B,W\le 2500,B+W\le n\times m\)


\(f_{i,j,x}\) 為用 \(x\) 個棋子佔 \(i\)\(j\) 列的方案數,則

\(ans=\sum_{i=1}^{n}\sum_{j=1}^{n-i}\sum_{k=1}^m\sum_{l=1}^{m-k}\binom{n}{i}\binom{n-i}{j}\binom{m}{k}\binom{m-k}{l}\times f_{i,k,B}\times f_{j,l,W}\)

可在 \(O(n^2m^2)\) 內求出答案,考慮求 \(f_{i,j,k}\) ,有兩種方法,都可以做到 \(O(n^2m^2)\)

dp

\(f_{n,m,x}\) 等於 \(\binom{n\times m}{x}\) 減去(剛好 \(i\)\(j\) 列放了的方案數),

其中 \(1\le i\le n,1\le j\le m,(i,j)\ne(n,m)\)

#include <bits/stdc++.h>
using namespace std;
typedef unsigned long long uLL;
typedef long double LD;
typedef long long LL;
typedef double db;
const LL P = 998244353;
const int N = 1e5 + 5;
int n, m, B, W;
LL fac[N], inv[N], fi[N], ans;
LL f1[55][55], f2[55][55];
inline LL C(LL n, LL m) {
    if (n < 0 || m < 0 || n < m) return 0;
    return fac[n] * fi[m] % P * fi[n - m] % P;
}
inline void init(int X, LL f[55][55]) {
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++) {
            f[i][j] = C(i * j, X);
            for (int k = 1; k <= n; k++)
                for (int l = 1; l <= m; l++) {
                    if (i == k && j == l) continue;
                    (f[i][j] += P - C(i, k) * C(j, l) % P * f[k][l] % P) %= P;
                }   
        }
}
int main() {
    fac[0] = fi[0] = 1;
    fac[1] = inv[1] = fi[1] = 1;
    for (int i = 2; i <= 100000; i++) {
        fac[i] = fac[i - 1] * i % P;
        inv[i] = (P - P / i) * inv[P % i] % P;
        fi[i] = fi[i - 1] * inv[i] % P;
    }
    scanf("%d%d%d%d", &n, &m, &B, &W);
    init(B, f1);
    init(W, f2);
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= n - i; j++)
            for (int k = 1; k <= m; k++)
                for (int l = 1; l <= m - k; l++)
                    (ans += C(n, i) * C(n - i, j) % P * C(m, k) % P * C(m - k, l) % P * f1[i][k] % P * f2[j][l] % P) %= P;
    printf("%lld", ans);
}

容斥原理

根據容斥原理,\(f_{n,m,x}=\sum (-1)^{i+j}\times\) (至少 \(i\)\(j\) 列沒有放的方案數)

即:

\[f_{n,m,x}=\sum_{i=0}^n\sum_{j=0}^m (-1)^{i+j}\times\binom{n}{i}\times\binom{m}{j}\times\binom{(n-i)\times(m-j)}{x} \]
#include <bits/stdc++.h>
using namespace std;
typedef unsigned long long uLL;
typedef long double LD;
typedef long long LL;
typedef double db;
const LL P = 998244353;
const int N = 1e5 + 5;
int n, m, B, W;
LL fac[N], inv[N], fi[N], ans;
LL f1[55][55], f2[55][55];
inline LL C(LL n, LL m) {
    if (n < 0 || m < 0 || n < m) return 0;
    return fac[n] * fi[m] % P * fi[n - m] % P;
}
inline void init(int X, LL f[55][55]) {
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++)
            for (int k = 0; k <= i; k++)
                for (int l = 0, v; l <= j; l++) {
                    v = 1;
                    if ((k + l) & 1) v = -1;
                    (f[i][j] += v * C(i, k) * C(j, l) % P * C((i - k) * (j - l), X) % P) %= P;
                    (f[i][j] += P) %= P;
                }
}
int main() {
    fac[0] = fi[0] = 1;
    fac[1] = inv[1] = fi[1] = 1;
    for (int i = 2; i <= 100000; i++) {
        fac[i] = fac[i - 1] * i % P;
        inv[i] = (P - P / i) * inv[P % i] % P;
        fi[i] = fi[i - 1] * inv[i] % P;
    }
    scanf("%d%d%d%d", &n, &m, &B, &W);
    init(B, f1);
    init(W, f2);
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= n - i; j++)
            for (int k = 1; k <= m; k++)
                for (int l = 1; l <= m - k; l++)
                    (ans += C(n, i) * C(n - i, j) % P * C(m, k) % P * C(m - k, l) % P * f1[i][k] % P * f2[j][l] % P) %= P;
    printf("%lld", ans);
}

G Range Pairing Query

\(n\) 個數 \(a_i\) ,每次詢問 \([l,r]\) ,若其中數出現次數 \(c_i\) ,求 \(\sum\lfloor\dfrac{c_i}{2}\rfloor\)

莫隊板題,略

Ex Random Painting

\(n\) 個格子初始為白色,\(m\) 個區間 \([L_i,R_i]\),每次從 \(m\) 個區間內隨機選出一個 \(x\)

將格子 \(L_x,L_x+1,\cdots,R_x\) 染成黑色,求將所有格子染成黑色的期望次數 \(\;mod\ 998244353\)

sol

part 1

\(f_i\) 為從 \(m\) 中選 \(i\) 個能覆蓋區間 \([1,n]\) 的方案數。

  • 則答案為 \(\sum_{i=0}^n (1-\dfrac{f_i}{\binom{m}{i}})\times \dfrac{m}{m-i}\)

為什麼?

前面一半:從 \(m\) 箇中選 \(i\) 個不覆蓋 \([1,n]\) 的概率,即不能終止

後面一半:選剩下 \(m-i\) 中選出一個沒有選過的期望次數

二者相乘:當前狀態為選了 \(i\) 條邊,合法的概率 \(\times\) 選一條未選過的邊的期望,轉移到狀態 \(i+1\)

一直轉移,得到答案

part 2

\(f_i\) ,先將線段雙關鍵排序

考慮 \(dp_{i,j,k}\) 表示前 \(i\) 條線段,選出 \(k\) 條的並集為 \([1,j]\) 的方案數

目標:\(f_k=dp_{m,n,k}\)

  • \(j<L_i-1\)\(dp_{i,j,k}=dp_{i-1,j,k}\)

  • \(L_i-1\le j<R_i\)\(dp_{i,j,k}=dp_{i-1,j,k}\)\(dp_{i-1,j,k-1}\rightarrow dp_{i,R_i,k}\)

  • \(R_i\le j\)

    \[\begin{aligned} dp_{i,j,k}=\begin{cases} dp_{i-1,j,k} & \left(k=0\right)\\ dp_{i-1,j,k-1}+dp_{i-1,j,k} & \left(0<k\right) \end{cases} \end{aligned} \]

j 倒敘列舉,保證 \(j=R_i\)\(L_i-1\le j<R_i\) 時不重複

可以在 \(O(nm^2)\) 的時間內解決

#include <bits/stdc++.h>
using namespace std;
typedef unsigned long long uLL;
typedef long double LD;
typedef long long LL;
typedef double db;
const int N = 405;
const LL P = 998244353;
int n, m, a[N], b[N];
struct rg { int l, r; } x[N];
inline bool cmp(rg A, rg B) {
    if (A.l ^ B.l) return A.l < B.l;
    return A.r < B.r;
}
LL fac[N], inv[N], fi[N], f[N][N][N], ans;
inline LL C(int n, int m) {
    if (n == m || !m) return 1;
    return fac[n] * fi[m] % P * fi[n - m] % P;
}
inline LL Pow(LL x, LL y) {
    register LL res = 1;
    for (; y; y >>= 1, x = x * x % P)
        if (y & 1) res = res * x % P;
    return res;
}
inline LL Inv(LL x) { return Pow(x, P - 2); }
int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= m; i++) scanf("%d%d", &x[i].l, &x[i].r);
    sort(x + 1, x + m + 1, cmp);
    for (int i = 0; i < m; i++) a[i] = x[i + 1].l, b[i] = x[i + 1].r;
    fac[1] = inv[1] = fi[1] = 1;
    for (int i = 2; i <= m; i++) {
        fac[i] = fac[i - 1] * i % P;
        inv[i] = (P - P / i) * inv[P % i] % P;
        fi[i] = fi[i - 1] * inv[i] % P;
    }
    f[0][0][0] = 1;
    for (int i = 0; i < m; i++) {
        for (int j = n; ~j; j--) {
            for (int k = 0; k <= m; k++) {
                if (j < a[i] - 1) f[i + 1][j][k] = f[i][j][k];
                else if (j <= b[i]) {
                    f[i + 1][j][k] = f[i][j][k];
                    if (k) (f[i + 1][b[i]][k] += f[i][j][k - 1]) %= P;
                } else {
                    f[i + 1][j][k] = f[i][j][k];
                    if (k) (f[i + 1][j][k] += f[i][j][k - 1]) %= P;
                }
            }
        }
    }
    for (int i = 0; i <= m; i++) {
        (ans += (1 - f[m][n][i] * Inv(C(m, i)) % P + P) % P * m % P * Inv(m - i) % P) %= P;
    }
    printf("%lld", ans);
}

虛擬賽 體驗

第一場 AT 居然是虛擬賽

只寫了 ABCEG ,發現其實 AT 是手速和思維一起的

比如比較 naive 的 D 由於時間沒有打

以後便是開始線上賽了

virtual judge 當時是前 30 ?大霧

也是一個新的練習平臺,新的開始,確信