2022.1.20 模拟赛

哈哈输出大样例能得的分都比打了 3.5h 得分高。

A. 「清华集训 2017」小 Y 和恐怖的奴隶主

期望dp + 矩阵乘法

看完题之后比较晕,没有想到把 1,2,3 血的怪的数量都记录下来,整个 4 维期望 dp,直接跳过了,有点亏。

设状态 \(dp[i][a][b][c]\),表示打了 \(i\) 次之后场上 1,2,3 血的怪物分别有 \(a, b, c\) 只。

暴力转移显然会 TLE。

不难发现,总共只有165 个状态,加上答案状态就是 166 个状态,所以可以编个号,然后压到一维里,使用矩乘加速。

code
#include <bits/stdc++.h>
#define ll long long

using namespace std;

namespace IO{
    inline ll read(){
        ll x = 0;
        char ch = getchar();
        while(!isdigit(ch)) ch = getchar();
        while(isdigit(ch)) x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
        return x;
    }

    template <typename T> inline void write(T x){
        if(x < 0) putchar('-'), x = -x;
        if(x > 9) write(x / 10);
        putchar(x % 10 + '0');
    }
}
using namespace IO;

const int N = 210;
const int mod = 998244353;
int T, m, s, n;
int id[13][13][13], inv[N];

inline int add(int x) {return x >= mod ? x - mod : x;}

inline int qpow(int a, int b){
    int res = 1;
    while(b){
        if(b & 1) res = 1ll * res * a % mod;
        a = 1ll * a * a % mod, b >>= 1;
    }
    return res;
}

struct matrix{
    int num[N][N];

    matrix() {memset(num, 0, sizeof(num));}

    void init() {for(int i = 1; i <= n; ++i) num[i][i] = 1;}

    friend matrix operator * (matrix a, matrix b){
        matrix c;
        for(int i = 1; i <= n; ++i)
            for(int j = 1; j <= n; ++j)
                for(int k = 1; k <= n; ++k)
                    c.num[i][j] = add(c.num[i][j] + 1ll * a.num[i][k] * b.num[k][j] % mod);
        return c;
    }
}f[64];
int ans[N], t[N];

inline void Mul(int u){
    for(int i = 1; i <= n; ++i) t[i] = 0;
    for(int i = 1; i <= n; ++i)
        for(int j = 1; j <= n; ++j)
            t[i] = add(t[i] + 1ll * ans[j] * f[u].num[j][i] % mod);
    for(int i = 1; i <= n; ++i) ans[i] = t[i];
}

int main(){
    T = read(), m = read(), s = read();
    for(int i = 1; i <= 9; ++i) inv[i] = qpow(i, mod - 2);
    for(int i = 0; i <= s; ++i)
        for(int j = 0; j <= (m >= 2 ? s - i : 0); ++j)
            for(int k = 0; k <= (m == 3 ? s - i - j : 0); ++k)
                id[i][j][k] = ++n;
    n++;
    f[0].num[n][n] = 1;
    for(int a = 0; a <= s; ++a)
        for(int b = 0; b <= (m >= 2 ? s - a : 0); ++b)
            for(int c = 0; c <= (m == 3 ? s - a - b : 0); ++c){
                int i = id[a][b][c], inv_i = inv[a + b + c + 1], t = (a + b + c < s);
                if(a) f[0].num[i][id[a - 1][b][c]] = 1ll * a * inv_i % mod;
                if(m == 2 && b) f[0].num[i][id[a + 1][b - 1 + t][c]] = 1ll * b * inv_i % mod;
                if(m == 3 && b) f[0].num[i][id[a + 1][b - 1][c + t]] = 1ll * b * inv_i % mod;
                if(m == 3 && c) f[0].num[i][id[a][b + 1][c - 1 + t]] = 1ll * c * inv_i % mod;
                f[0].num[i][i] = f[0].num[i][n] = inv_i;
            }
    for(int i = 1; i <= 60; ++i) f[i] = f[i - 1] * f[i - 1];
    while(T--){
        ll x = read();
        for(int i = 1; i <= n; ++i) ans[i] = 0;
        ans[id[m == 1][m == 2][m == 3]] = 1;
        for(int i = 0; i <= 60; ++i)
            if((x >> i) & 1) Mul(i);
        write(ans[n]), puts("");
    }
    return 0;
}

B. 「一本通 4.4 练习 4」跳跳棋

巨大难思维 + LCA

谁能想到这题跳棋的情况可以建成一棵树???

20pts bfs 暴力滚粗。

注意题目里:一次只允许跳过一个棋子。

(考场上虽然看到了,但是没反应过来,还以为是只能跳一次的意思……)

所以中间的点可以向两边跳,两边的点只有一个能往中间跳(只有距离中间那个点近的点可以向中间跳)。

把三个点的位置三元组 \((x, y, z)\) 存到一个节点里,那么是如何建树的呢?

把向中间跳的那个三元组当父亲节点,向外跳的两个当儿子节点,然后两点之间的距离就是跳的次数。

但是怎么快速求 LCA 呢?三元组普通的倍增什么的是不行的,所以先把深度大的点跳到跟另一个点深度相同的高度,然后二分向上跳的步数,再判断。

code
#include <bits/stdc++.h>

using namespace std;

namespace IO{
    inline int read(){
        int x = 0, f = 1;
        char ch = getchar();
        while(!isdigit(ch)) {if(ch == '-') f = -1; ch = getchar();}
        while(isdigit(ch)) x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
        return x * f;
    }

    template <typename T> inline void write(T x){
        if(x > 9) write(x / 10);
        putchar(x % 10 + '0');
    }
}
using namespace IO;

struct node{
    int x, y, z;

    inline void init(){
        if(x > y) swap(x, y);
        if(x > z) swap(x, z);
        if(y > z) swap(y, z);
    }

    friend bool operator != (node a, node b){
        return (a.x ^ b.x) | (a.y ^ b.y) | (a.z ^ b.z);
    }

    friend bool operator == (node a, node b){
        return (a.x == b.x) & (a.y == b.y) & (a.z == b.z);
    }
}a, b, A, B, C;
int tot1, tot2, ans;

inline int dfs(int x, int y, int z){
    int d1 = y - x, d2 = z - y, cnt = 0, d;
    if(d1 > d2){
        cnt = d1 / d2, d = d1 % d2;
        if(!d) d += d2, cnt--;
        cnt += dfs(x, x + d, x + d + d2);
    }else if(d1 < d2){
        cnt = d2 / d1, d = d2 % d1;
        if(!d) d += d1, cnt--;
        cnt += dfs(z - d - d1, z - d, z);
    }else C = (node){x, y, z};
    return cnt;
}

inline void update(int x, int y, int z, int step){
    if(!step) return C = (node){x, y, z}, void();
    int d1 = y - x, d2 = z - y, cnt = 0, d;
    if(d1 > d2){
        cnt = d1 / d2, d = d1 % d2;
        if(!d) d += d2, cnt--;
        if(step >= cnt) update(x, x + d, x + d + d2, step - cnt);
        else update(x, x + d + d2 * (cnt - step), x + d + d2 * (cnt - step + 1), 0);
    }else if(d1 < d2){
        cnt = d2 / d1, d = d2 % d1;
        if(!d) d += d1, cnt--;
        if(step >= cnt) update(z - d - d1, z - d, z, step - cnt);
        else update(z - d - d1 * (cnt - step + 1), z - d - d1 * (cnt - step), z, 0);
    }else C = (node){x, y, z};
}

inline bool check(int step){
    update(a.x, a.y, a.z, step), A = C;
    update(b.x, b.y, b.z, step), B = C;
    return A == B;
}

inline int solve(){
    int l = 0, r = min(tot1, tot2), res;
    while(l <= r){
        int mid = (l + r) >> 1;
        if(check(mid)) r = mid - 1, res = mid;
        else l = mid + 1;
    }
    return (res << 1) + ans;
}

int main(){
    a = (node){read(), read(), read()}, b = (node){read(), read(), read()};
    a.init(), b.init();
    tot1 = dfs(a.x, a.y, a.z), A = C;
    tot2 = dfs(b.x, b.y, b.z), B = C;
    // cout << tot1 << " " << tot2 << endl;
    if(A != B) return puts("NO"), 0;
    if(tot1 < tot2){
        ans += tot2 - tot1;
        update(b.x, b.y, b.z, tot2 - tot1), b = C;
    }else if(tot1 > tot2){
        ans += tot1 - tot2;
        update(a.x, a.y, a.z, tot1 - tot2), a = C;
    }
    // cout << a.x << " " << a.y << " " << a.z << endl;
    printf("YES\n%d\n", solve());
    return 0;
}

C. Calc

dp + 拉格朗日插值

先考虑暴力 dp

设 \(dp_{i, j}\) 表示前 \(i\) 个数用了 \(1 \sim j\) 的和,转移方程比较显然:

我们只处理递增的序列,分类讨论第 \(i\) 个数取不取 \(j\)。

\[dp_{i, j} = dp_{i - 1, j - 1} \times j + dp_{i, j - 1} \]

经过一系列推导,我们发现 \(dp_{n, i}\) 是关于 \(i\) 的一个 \(2n\) 次多项式,所以直接拉格朗日插值求一下 \(dp_{n, A}\) 即可。

code
#include <bits/stdc++.h>
#define ll long long

using namespace std;

namespace IO{
    inline int read(){
        int x = 0;
        char ch = getchar();
        while(!isdigit(ch)) ch = getchar();
        while(isdigit(ch)) x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
        return x;
    }

    template <typename T> inline void write(T x){
        if(x > 9) write(x / 10);
        putchar(x % 10 + '0');
    }
}
using namespace IO;

const int N = 510;
ll A, n, mod, ans;
ll fac[N], f[N][N << 1];

inline ll qpow(ll a, ll b){
    ll res = 1;
    while(b){
        if(b & 1) res = res * a % mod;
        a = a * a % mod, b >>= 1;
    }
    return res;
}

inline void lagrange(){
    for(int i = 0; i <= (n << 1); ++i){
        ll t1 = 1, t2 = 1;
        for(int j = 0; j <= (n << 1); ++j)
            if(i != j) t1 = t1 * (A - j + mod) % mod, t2 = t2 * (i - j + mod) % mod;
        ans = (ans + f[n][i] * t1 % mod * qpow(t2, mod - 2) % mod) % mod;
    }
}

int main(){
    A = read(), n = read(), mod = read();
    fac[0] = 1;
    for(int i = 1; i <= n; ++i) fac[i] = fac[i - 1] * i % mod;
    for(int i = 0; i <= (n << 1); ++i) f[0][i] = 1;
    for(int i = 1; i <= n; ++i)
        for(int j = 1; j <= (n << 1); ++j)
            f[i][j] = (f[i - 1][j - 1] * j % mod + f[i][j - 1]) % mod;
    lagrange();
    write(ans * fac[n] % mod), puts("");
    return 0;
}
上一篇:今天的码农女孩做了关于日期的练习 2022/1/6


下一篇:python脚本小技巧