20240220模拟赛

20240220 模拟赛

T1

给定 \(n,m,k\),要向一个 \(n\times m\) 的矩阵所有位置填上一个 \([1,k]\) 的整数。问有多少种填法,使得每一行都不相同,每一列都不相同。

\(n,m\le5000,k\le10^5\)

先满足行的限制。设 \(f(x)\) 为填了 \(x\) 列,且每一行都不相同的方案数。显然 \(f(x)=(k^x)^{\underline{n}}\)

然后设 \(g(x)\) 为填了 \(x\) 列,且满足题目条件的方案数。

可以发现,对于 \(f(x)\) 中的某种填法,若这种填法恰好有 \(t\) 种不同的列,那么对于每一种我们只保留第一个,其余全部删去。然后就可以得到 \(g(t)\) 的一种方案。这就相当于 \(x\) 个元素(两两不同)划分进 \(t\) 个不同的集合的方案数,即 \(x\brace{t}\)。故:

\[f(x)=\sum\limits_{t=1}^x {x\brace{t}}g(t) \]

我们要求 \(g(m)\),斯特林反演:

\[g(x)=\sum\limits_{i=1}^x(-1)^{x-i}{x\brack{t}}f(t) \]

暴力算即可。时间复杂度 \(O(nm)\)

Code
#pragma GCC optimize("Ofast")
#include<bits/stdc++.h>
const int N = 5053, mod = 1004535809;

inline int read() {
    char ch = getchar();
    int x = 0;
    while (ch < '0' || ch > '9') ch = getchar();
    while (ch >= '0' && ch <= '9') x = x * 10 + ch - '0', ch = getchar();
    return x;
}

int T, n, m, k;
int S[N][N], pw[N];
inline int g(int m) {
    int ret = 1;
    for (int i = 0; i < n; i++)
        ret = 1ll * ret * (pw[m] - i) % mod;
    return ret;
}

int main() {
    freopen("square.in", "r", stdin);
    freopen("square.out", "w", stdout);
    S[0][0] = pw[0] = 1;
    for (int i = 1; i < N; i++)
        for (int j = 1; j < N; j++)
            S[i][j] = (1ll * (i - 1) * S[i - 1][j] + S[i - 1][j - 1]) % mod;
    T = read();
    while (T--) {
        n = read(), m = read(), k = read();
        for (int i = 1; i <= m; i++) pw[i] = 1ll * pw[i - 1] * k % mod;

        int sum = 0;
        for (int i = 0; i <= m; i++)
            (sum += 1ll * g(i) * (((m - i) & 1) ? (mod - 1) : 1) % mod * S[m][i] % mod) %= mod;
        printf("%d\n", sum);
    }
    return 0;
}

T2

给定两个长度分别为 \(n,m\) 的整数序列 \(A,B\),值域为 \([1,k]\)。求出一个值域为 \([1,k]\) 的整数序列 \(C\),满足既不是 \(A\) 的子序列,也不是 \(B\) 的子序列。求 \(C\) 的最小长度。

\(1\le n,m,k\le4000\)

写的不是正解。

可以猜测答案不是很大。建出 \(A,B\) 的子序列自动机,然后直接跑 bfs 即可。

Code
#pragma GCC optimize("Ofast")
#include <bits/stdc++.h>
const int M = 4010;

inline int read() {
    char ch = getchar();
    int x = 0;
    while (ch < '0' || ch > '9') ch = getchar();
    while (ch >= '0' && ch <= '9') x = x * 10 + ch - '0', ch = getchar();
    return x;
}

int A[M][M], B[M][M];
int n, m, k, a[M], b[M], t[M];

struct Node { int a, b, dis; };
bool vis[M][M];

inline int bfs() {
    vis[0][0] = true;
    std::queue<Node> q;
    q.push((Node) {0, 0, 0});
    while (!q.empty()) {
        Node f = q.front(); q.pop();
        if (f.a == n + 1 && f.b == m + 1)
            return f.dis;
        for (int c = 1; c <= k; c++) {
            int a0 = A[f.a][c], b0 = B[f.b][c];
            if (!vis[a0][b0]) {
                vis[a0][b0] = true;
                q.push((Node) {a0, b0, f.dis + 1});
            }
        }
    }
    assert(true);
    return 0;
}

int main() {
    freopen("subsequence.in", "r", stdin);
    freopen("subsequence.out", "w", stdout);
    n = read(), m = read(), k = read();
    for (int i = 1; i <= n; i++) a[i] = read();
    for (int j = 1; j <= m; j++) b[j] = read();
    
    for (int i = 1; i <= k; i++) t[i] = A[n + 1][i] = n + 1;
    for (int i = n; i >= 0; i--) {
        for (int j = 1; j <= k; j++) A[i][j] = t[j];
        t[a[i]] = i;
    }
    for (int i = 1; i <= k; i++) t[i] = B[m + 1][i] = m + 1;
    for (int i = m; i >= 0; i--) {
        for (int j = 1; j <= k; j++) B[i][j] = t[j];
        t[b[i]] = i;
    }

    printf("%d\n", bfs());
    return 0;
}

T3

给定 \(n,k\),问有多少张大小为 \(n\) 的有标号竞赛图,存在大小为 \(k\) 的简单环。

\(3\le n,k\le10^5\)

牛逼题。部分分很有启发性。

以下的图都是有标号的。

\(\Large k=3\)

即问存在三元环的竞赛图个数。

结论1:竞赛图若存在环,则一定存在三元环。

归纳证明,假设现在找到了一个 \(k\) 元环。当 \(k=4\) 时结论显然成立。当 \(k\ge5\),假设找到的环是 \(a_1a_2\cdots a_k\),考虑 \(a_1\)\(a_3\) 之间的连边。若 \(a_1\to a_3\),那么 \(a_1a_3a_4\cdots a_k\) 又形成了一个环,即归为 \(k-1\) 元环。若 \(a_3\to a_1\),那么 \(a_1a_2a_3\) 就是一个三元环。

结论2:\(1\sim n\) 的排列和 \(n\) 个点的无环竞赛图一一对应。

将排列看成拓扑序。设这个排列为 \(A\),对于每个 \(i<j\),都连边 \(A_i\to A_j\),即可构造出拓扑序为 \(A\) 的竞赛图。

容易证明无法构造出另一张拓扑序为 \(A\) 的竞赛图,因为点与点之间都有连边,

\(k=3\) 时,考虑容斥。由结论1可知,答案应该是 \(2^{\binom{n}{2}}\) 减去无环竞赛图的个数。由结论2可知无环竞赛图的数量为 \(n!\)。故答案是 \(2^\binom{n}{2}-n!\)

\(\Large k=n\)

即要求强连通竞赛图的个数。

结论3:竞赛图强连通分量缩点并去除重边后,是一张无环竞赛图。

这个比较显然,因为强连通分量之间必然会有连边,而且又缩成了 DAG。

根据结论2可以得出 DAG 竞赛图的一种构造方法,将每个点看成一个强连通分量,就变成了任意竞赛图的构造方法。

对于任意一张竞赛图,对于其缩点后的拓扑序第一的 DCC,将它向其他 DCC 的连边删除,就将整个图拆成了一张强连通竞赛图和一张任意竞赛图。

故可以设 \(f(n)\)\(n\) 个点的强连通竞赛图的个数,那么有:

\[\large 2^{\binom{n}{2}}=\sum\limits_{i=1}^{n}\binom{n}{i}f(i)\times2^{\binom{n-i}{2}} \]

即枚举拓扑序第一的强连通分量大小 \(i\),然后让一张大小为 \(i\) 的强连通竞赛图中的所有点,向一张 \(n-i\) 个点的普通竞赛图中的所有点连边,构成一张大小为 \(n\) 的竞赛图。

\(g(n)=2^{\binom{n}{2}}\),两边同时除以 \(n!\)

\[\frac{g(n)}{n!}=\sum\limits_{i=1}^n\frac{f(i)}{i!}\cdot\frac{g(n-i)}{(n-i)!} \]

设 EGF:\(F(x)=\sum\limits_{i=0}\frac{f(i)}{i!}x^i,G(x)=\sum\limits_{i=0}\frac{g(i)}{i}x^i\)

\[[x^n]G(x)=\sum\limits_{i=1}^n[x^i]F(x)\cdot[x^{n-i}]G(x)\\\begin{aligned} (F\times G)(x)=&\sum\limits_{i=0}x^i\sum\limits_{j=0}^i[x^j]F(x)\cdot[x^{i-j}]G(x)\\ =&G(x)-\frac{g(0)}{0!}x^0=G(x)-1\\ F(x)=&1-\frac{1}{G(x)} \end{aligned}\]

求逆即可。

\(\Large k\text{ 任意}\)

结论4:竞赛图中一个大小为 \(k(3\le k\le n)\) 的强连通分量中,\(\forall k_0=[3,k]\),一定存在 \(k_0\) 个点的强连通分量。

归纳证明。考虑其中一个点 \(x\),如果将这个点删去,那么就会变成 \(k-1\) 个点的竞赛图。将这 \(k-1\) 个竞赛图 DCC 缩点。设剩下 \(p\) 个点,按照拓扑序为 \(a_1a_2\cdots a_p\),那么原来的图必然有 \(x\to a_1,a_m\to x\)(不然 \(a_1\) 没有入度或 \(a_m\) 没有出度,都不可能形成 DCC)。

当对于更小的 \(k\) 都成立时,对于环 \(xa_1a_2\cdots a_px\),让某个强连通分量 \(a_i\) 遍历时跳过一个点(或者当 \(a_i\) 只有一个点时,直接跳过 \(a_i\))。这样就有大小为 \(k-1\) 的强连通分量,结论得证。

\(k\) 任意时考虑容斥,即求有多少个图所有强连通分量的大小都小于 \(k\)。设 \(n\) 个点时答案为 \(h(n)\)

\[h(n)=\sum\limits_{i=1}^{\min(n,k-1)}\binom{n}{i}f(i)h(n-i) \]

\(\forall i\ge k,f(i)\gets0\),就可以将求和上界变成 \(n\)。然后相同的处理:

\[\frac{h(n)}{n!}=\sum\limits_{i=1}^n\frac{f(i)}{i!}\cdot\frac{h(n-i)}{(n-i)!} \]

\(H(x)=\sum\limits_{i=0}\frac{h(i)}{i!}x^i\),容易求得 \(H(x)=\frac{1}{1-F(x)}\)

总时间复杂度 \(O(n\log n)\)

Code
#include <bits/stdc++.h>
const int M = 4e5 + 10, mod = 998244353;

inline int qpow(int b, long long p) {
    int ret = 1;
    for (; p; p >>= 1) {
        if (p & 1)
            ret = 1ll * ret * b % mod;
        b = 1ll * b * b % mod;
    }
    return ret;
}

namespace Poly {
    const int g = 3, inv_g = qpow(3, mod - 2);
    int rev[M], last_n;
    inline void NTT(int* f, bool x, int n) {
        if (n != last_n)
            for (int i = 0; i < n; i++)
                rev[i] = (rev[i >> 1] >> 1) | ((i & 1) ? (n >> 1) : 0);
        for (int i = 0; i < n; i++)
            if (i > rev[i]) std::swap(f[i], f[rev[i]]);
        for (int k = 2, l = 1; k <= n; k <<= 1, l <<= 1) {
            int G = qpow(x ? g : inv_g, (mod - 1) / k);
            for (int i = 0; i < n; i += k)
                for (int g = 1, j = 0; j < l; j++, g = 1ll * g * G % mod) {
                    int tmp = 1ll * f[i + j + l] * g % mod;
                    f[i + j + l] = (f[i + j] - tmp + mod) % mod;
                    f[i + j] = (f[i + j] + tmp) % mod;
                }
        }
        if (!x) {
            for (int i = 0, I = qpow(n, mod - 2); i < n; i++)
                f[i] = 1ll * f[i] * I % mod;
        }
        last_n = n;
    }
    inline void Multi(int* a, int* b, int n) {
        for (int i = 0; i < n; i++)
            a[i] = 1ll * a[i] * b[i] % mod;
    }
    inline void Inv(int* f, int n) {
        static int g[M], g0[M], g2[M], tmp[M];
        g[0] = qpow(f[0], mod - 2);
        for (int k = 2; k <= n; k <<= 1) {
            memcpy(g0, g, sizeof(int) * (k >> 1));
            for (int i = 0; i < (k >> 1); i++)
                g2[i] = (g[i] << 1) % mod;
            memcpy(tmp, f, sizeof(int) * k);
            NTT(g0, true, k << 1);
            Multi(g0, g0, k << 1);
            NTT(tmp, true, k << 1);
            Multi(g0, tmp, k << 1);
            NTT(g0, false, k << 1);
            memset(g0 + k, 0, sizeof(int) * k);
            for (int i = 0; i < k; i++)
                g[i] = (g2[i] - g0[i] + mod) % mod;
        }
        memcpy(f, g, sizeof(int) * n);
        memset(g, 0, sizeof(int) * (n << 1));
        memset(g0, 0, sizeof(int) * (n << 1));
        memset(g2, 0, sizeof(int) * (n << 1));
        memset(tmp, 0, sizeof(int) * (n << 1));
    }
}

int n, m, k, f[M], g[M], h[M];
int fac[M], ifac[M];

int main() {
    freopen("tournament.in", "r", stdin);
    freopen("tournament.out", "w", stdout);
    scanf("%d%d", &n, &k);
    for (m = 1; m <= n; m <<= 1);
    for (int i = 0; i <= n; i++)
        g[i] = qpow(2, 1ll * i * (i - 1) / 2);
    fac[0] = 1;
    for (int i = 1; i <= m; i++)
        fac[i] = 1ll * fac[i - 1] * i % mod;
    ifac[m] = qpow(fac[m], mod - 2);
    for (int i = m; i >= 1; i--)
        ifac[i - 1] = 1ll * ifac[i] * i % mod;
    for (int i = 0; i <= n; i++)
        g[i] = 1ll * g[i] * ifac[i] % mod;
    memcpy(f, g, sizeof(int) * (n + 1));
    Poly::Inv(g, m);
    memcpy(h, g, sizeof(int) * k);
    Poly::Inv(h, m);
    std::cout << 1ll * (f[n] - h[n] + mod) % mod * fac[n] % mod << '\n';
    return 0;
}
posted @ 2024-02-22 15:46  zzxLLL  阅读(18)  评论(0)    收藏  举报