Exam Records 6
10.31
D
题目描述
“真开心呢,凤同学。”——Asahina Mafuyu。
给出一个长度为 \(n\) 的排列 \(P\) 和一个定义在集合 \(\{1, 2, 3,\cdots, n − 1\}\) 上的函数 \(f\) ,我们称该排列“生成”的图为这样的一张图 \(G\):
- \(G\) 具有编号为 \(1\) 到 \(n\) 的 \(n\) 个顶点。
- 如果 \(i < j\) 且 \(P_i > P_j\) (即 \((i, j)\) 为一个逆序对),那么在 \(G\) 中存在一条无向边 \((i, j)\)。
在此基础上,如果 \(D\) 是长度为正偶数的顶点序列 \(\{D_1 , D_2 , \cdots D_{2m-1} , D_{2m} \}\),其中 \(m\) 是任意的正整数,并且满足以下性质:
- \(\forall 1 \le k < 2m\),\(D_k < D_{k+1}\)。
- \(\forall 1 \le k \le m\),存在一条无向边 \((D_{2k-1}, D_{2k})\)。
- \(\forall 1 \le j, k \le m\),\(j \neq k\),图中不存在一条起点是 \(D_{2j}\),终点是 \(D_{2k}\) 的简单路径。
(即选择若干条顶点编号递增的边,并且任意两条边不在同一个连通块中)
则我们称序列 \(D\) 是“开心”的,记该序列的权值为 \(\prod _{k=1}^m f (P_{D_{2k-1}} - P_{D_{ 2k}})\)。
定义一个排列的权值为所有“开心”的序列 \(D\) 的权值之和,即
现在,你需要求出所有排列的权值之和对 \(998244353\) 取模的结果。
\(1 \le n \le 5000\),\(1 \le f(i) \le 10^9\)。
排列图的连通块是一个区间。
对于每一个连通块求出答案。设 \(g_i\) 表示假设 \(1 \sim i\) 中的所有排列都是一个连通块的权值和。\(f_i\) 表示 \(1 \sim i\) 的排列是一个连通块的权值和。
然后对于每个连通块,可以跳过,可以经过,可以从它开始。
代码
#include <cstdio>
#include <algorithm>
#define int long long
using namespace std;
const int N = 5005;
const int mod = 998244353;
const int inv = mod + 1 >> 1;
int n, a[N], F[N], fac[N], f[N][2], g[N][2];
// f[i][0], g[i][0] 表示方案数,f[i][1], g[i][1] 表示权值和。
signed main()
{
freopen("graph.in", "r", stdin);
freopen("graph.out", "w", stdout);
scanf("%lld", &n);
for(int i = 1; i < n; ++ i)
scanf("%lld", &a[i]);
fac[0] = 1;
for(int i = 1; i <= n; ++ i)
fac[i] = fac[i - 1] * i % mod;
g[0][0] = 1;
for(int i = 1; i <= n; ++ i)
g[i][0] = g[i - 1][0] * i % mod;
for(int i = 2; i <= n; ++ i)
for(int j = 1; j < i; ++ j)
g[i][1] = (g[i][1] + a[j] * (i - j) % mod * fac[i - 2] % mod * i % mod * (i - 1) % mod * inv) % mod;
f[1][0] = 1;
for(int i = 2; i <= n; ++ i)
{
f[i][0] = g[i][0], f[i][1] = g[i][1];
for(int j = 1; j < i; ++ j)
{
f[i][0] = (f[i][0] - g[j][0] * f[i - j][0]) % mod;
f[i][1] = (f[i][1] - g[j][0] * f[i - j][1] - g[j][1] * f[i - j][0]) % mod;
}
}
for(int i = 1; i <= n; ++ i)
for(int j = 0; j < i; ++ j)
F[i] = (F[i] + F[j] * f[i - j][1] + F[j] * f[i - j][0] + g[j][0] * f[i - j][1]) % mod;
printf("%lld\n", (F[n] + mod) % mod);
return 0;
}
11.1
C
题目描述
彩梦喜欢一个集合 \(S\),当且仅当存在另一个集合 \(T\) ,使得 \((S, T)\) 满足以下条件:
- \(\lvert S \rvert = n, \lvert T \rvert = m, 1 \in S\)。
- \(S \subseteq [1, nm] \cap \mathbb{Z}, T \subseteq \mathbb{Z}\)。
- \(\{i + j \mid i \in S, j \in T \} = \{1, 2, \cdots , nm\}\)。
给定 \(n, m\),求彩梦喜欢的集合数量对质数 \(998244353\) 取模的值。
为了方便选手,\(n\) 和 \(m\) 都使用唯一分解形式给出。
给定的 \(n, m\) 分别为 \(\prod p_i^{x_i}\) 和 \(\prod p_i^{y_i}\)。
\(0 \le \sum_{x_i},\sum_{y_i} \le 2\times 10^5\)。
如何判断一个集合 \(S\) 是否合法。
根据 \(S\) 构造一个长为 \(nm\) 的 \(01\) 字符串,字符串第 \(i\) 位为 \(1\) 当且仅当 \(i \in S\)。可以重复以下两种操作。
-
如果对于 \(k\) 满足 \([ki,k(i+1)-1]\) 全部相同,那么将 \([ki,k(i+1)-1]\) 替换为一个字符。
-
找到间隔最小的一对 \(1\),设间隔为 \(k\),将所有长度为 \(k\) 的 \(10\cdots 0\) 替换为 \(1\),所有长度为 \(k\) 的 \(0 \cdots 0\) 替换为 \(0\)。
如果字符串最终能变成 \(1\) 则合法。
记二元组 \((x,y)\) 表示字符串中 \(1\) 的数量和串长。相当于从 \((1,1)\) 开始轮流进行以下操作,答案为最终变成 \((n,nm)\) 的方案数。
-
\((x,y) \to (kx, ky)\)。
-
\((x, y) \to (x, ky)\)。
一操作和二操作都可以当做操作序列的开头,也都可以当做操作序列的结尾。
因为最终要变成 \((n,nm)\),所以一操作 \(\prod k = n\),二操作 \(\prod k = m\)。发现两种操作独立。对于每一种操作设 \(f_i\) 表示进行 \(i\) 次操作的方案数。
比如第一种操作,相当于有一些种球,第 \(x\) 种球有 \(a_x\) 个,要放到 \(i\) 个不同盒子,不可空的方案数。
二项式反演,设 \(g_i\) 为可空的方案数。由于 \(\sum {a_i} \le 2\times 10^5\),所以不同的 \(a_i\) 只有根号种,\(g_i\) 可以直接计算。求 \(f_i\) 套 FFT 即可。
代码
#include <cstdio>
#include <cassert>
#include <algorithm>
#define int long long
using namespace std;
const int N = 600005;
const int mod = 998244353;
int n = 600000, m, ans, lim, l, I, rev[N], a[N], b[N], f[N], g[N], h[N], fac[N], ifa[N];
int Pow(int x, int y)
{
int r = 1;
for(; y; y >>= 1, x = x * x % mod)
if(y & 1)
r = r * x % mod;
return r;
}
void init(int n)
{
for(l = 0, lim = 1; lim < n; lim <<= 1, ++ l);
I = Pow(lim, mod - 2);
for(int i = 0; i < lim; ++ i)
rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << l - 1);
}
void NTT(int *a, int type)
{
for(int i = 0; i < lim; ++ i)
if(i < rev[i])
swap(a[i], a[rev[i]]);
for(int i = 1; i < lim; i <<= 1)
{
int x = Pow(type ? 3 : (mod + 1) / 3, (mod - 1) / (i << 1));
for(int j = 0; j < lim; j += (i << 1))
{
for(int k = 0, y = 1; k < i; ++ k, y = y * x % mod)
{
int p = a[j + k], q = y * a[i + j + k] % mod;
a[j + k] = (p + q) % mod;
a[i + j + k] = (p - q) % mod;
}
}
}
if(!type)
for(int i = 0; i < lim; ++ i)
a[i] = a[i] * I % mod;
}
int C(int n, int m)
{
return fac[n] * ifa[m] % mod * ifa[n - m] % mod;
}
signed main()
{
freopen("set.in", "r", stdin);
freopen("set.out", "w", stdout);
fac[0] = ifa[0] = 1;
for(int i = 1; i <= n; ++ i)
fac[i] = fac[i - 1] * i % mod;
ifa[n] = Pow(fac[n], mod - 2);
for(int i = n - 1; i >= 0; -- i)
ifa[i] = ifa[i + 1] * (i + 1) % mod;
scanf("%lld", &m);
for(int i = 1, x, u, v; i <= m; ++ i)
scanf("%lld%lld%lld", &x, &u, &v), ++ a[u], ++ b[v];
for(int i = 1; i <= 2e5; ++ i)
f[i] = g[i] = 1;
for(int i = 1; i <= 2e5; ++ i)
if(a[i])
for(int j = 1; j <= 2e5; ++ j)
f[j] = f[j] * Pow(C(i + j - 1, j - 1), a[i]) % mod;
for(int i = 1; i <= 2e5; ++ i)
if(b[i])
for(int j = 1; j <= 2e5; ++ j)
g[j] = g[j] * Pow(C(i + j - 1, j - 1), b[i]) % mod;
for(int i = 0; i <= 2e5; ++ i)
{
f[i] = f[i] * ifa[i] % mod;
g[i] = g[i] * ifa[i] % mod;
h[i] = ifa[i] * (i & 1 ? -1 : 1) % mod;
}
init(4e5 + 1);
NTT(f, 1);
NTT(g, 1);
NTT(h, 1);
for(int i = 0; i < lim; ++ i)
{
f[i] = f[i] * h[i] % mod;
g[i] = g[i] * h[i] % mod;
}
NTT(f, 0);
NTT(g, 0);
for(int i = 0; i < lim; ++ i)
{
f[i] = f[i] * fac[i] % mod;
g[i] = g[i] * fac[i] % mod;
}
for(int i = 1; i <= 2e5; ++ i)
{
ans = (ans + 2 * f[i] * g[i]) % mod;
if(i)
ans = (ans + f[i] * g[i - 1]) % mod;
if(i != 2e5)
ans = (ans + f[i] * g[i + 1]) % mod;
}
printf("%lld\n", (ans + mod) % mod);
return 0;
}