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}\)。故:
我们要求 \(g(m)\),斯特林反演:
暴力算即可。时间复杂度 \(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\) 个点的强连通竞赛图的个数,那么有:
即枚举拓扑序第一的强连通分量大小 \(i\),然后让一张大小为 \(i\) 的强连通竞赛图中的所有点,向一张 \(n-i\) 个点的普通竞赛图中的所有点连边,构成一张大小为 \(n\) 的竞赛图。
令 \(g(n)=2^{\binom{n}{2}}\),两边同时除以 \(n!\):
设 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\),
求逆即可。
\(\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)\):
令 \(\forall i\ge k,f(i)\gets0\),就可以将求和上界变成 \(n\)。然后相同的处理:
设 \(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;
}

浙公网安备 33010602011771号