CTS2019填坑
d1t1
一个结论:\(n\)个位置,把\(1\sim n\)等概率随机填入这\(n\)个位置中。给定\(m\)个子集\(A_1,A_2,\dots,A_m\)和\(m\)个位置\(a_1,a_2,\dots,a_m\),满足\(a_i\in A_i,A_{i+1}\subset A_i,a_i\notin A_{i+1}\),求对于每个\(i\)都满足\(a_i\)是\(A_i\)中最大值的概率。这个问题下每个限制是独立的,证明比较显然,因为无论\(a_i\)填什么,\(A_{i+1}\setminus a_i\)中的每个元素都是本质相同的。
回到原题。首先可以容斥,强制至少有\(i\)个极大值时,按从大到小的顺序考虑这些极大值。对于每一个极大值,它可以填的位置数就是和之前的极大值没有任何一维相同的位置数,可以快速计算。原本的限制是对于每一个极大值,它是它的控制范围中最大的数,但是这样每个极大值不独立。可以发现,按从大到小的顺序考虑时,第\(j\)个极大值一定也大于第\(j+1,j+2,\dots,i\)个极大值的控制范围中所有数。这样就可以将问题转化为上述结论中子集的形式,可以独立计算满足最大数限制的概率。
#include<bits/stdc++.h>
using namespace std;
const int N = 5e6 + 10;
const int mod = 998244353;
typedef long long ll;
int f[N], fac[N], ifac[N], prd[N], iv[N], sz[N];
int C(int n, int m) {
return (ll)fac[n] * ifac[m] % mod * ifac[n - m] % mod;
}
int qpow(int a, int b) {
int ret = 1;
while(b) {
if(b & 1) {
ret = (ll)ret * a % mod;
}
a = (ll)a * a % mod, b >>= 1;
}
return ret;
}
int main() {
int T;
cin >> T;
fac[0] = sz[0] = f[0] = prd[0] = 1;
for(int i = 1; i < N; i++) {
fac[i] = (ll)fac[i - 1] * i % mod;
}
ifac[N - 1] = qpow(fac[N - 1], mod - 2);
for(int i = N - 2; ~i; i--) {
ifac[i] = (ll)ifac[i + 1] * (i + 1) % mod;
}
while(T--) {
int n, m, k, l;
cin >> n >> m >> l >> k;
if(n > m) {
swap(n, m);
}
if(n > l) {
swap(n, l);
}
for(int i = 1; i <= n; i++) {
prd[i] = (ll)prd[i - 1] * (sz[i] = ((ll)i * i % mod * i + ((ll)n * m + (ll)n * l + (ll)m * l) % mod * i - (ll)i * i % mod * (n + m + l)) % mod) % mod;
}
iv[n] = qpow(prd[n], mod - 2);
for(int i = n - 1; i; i--) {
iv[i] = (ll)iv[i + 1] * sz[i + 1] % mod;
}
for(int i = n; i; i--) {
sz[i] = (ll)prd[i - 1] * iv[i] % mod;
}
int t = n;
for(int i = 1; i <= t; i++, --n, --m, --l) {
f[i] = (ll)f[i - 1] * sz[i] % mod * n % mod * m % mod * l % mod;
}
int ans = 0;
for(int i = k; i <= t; i++) {
ans = (ans + 1ll * C(i, k) * f[i] * ((i - k) & 1 ? -1 : 1)) % mod;
}
cout << (ans + mod) % mod << '\n';
}
return 0;
}
d1t2
容斥以后变为钦定至少\(i\)个为奇数的方案数,式子是\(f_i=n![x^n]\binom{D}{i}(\frac{e^x-e^{-x}}{2})^ie^{x(D-i)}\)。如果能想办法将式子中的\(e\)结合在一起,那么\([x^n]e^x\)就可以直接计算了。因为括号将\(e\)隔开了,所以考虑用二项式定理拆括号,可以发现拆开后所有\(e\)可以结合在一起。然后卷积+二项式反演就行了。
#include<bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
const int mod = 998244353;
int d, n, m, len = 1, r[N], l = 0, fac[N], ifac[N], ib[N], f[N], g[N];
int qpow(int a, int b) {
int ret = 1;
while(b) {
if(b & 1) {
ret = 1ll * ret * a % mod;
}
a = 1ll * a * a % mod, b >>= 1;
}
return ret;
}
void NTT(int *a, int f) {
for(int i = 0; i < len; i++) if(i < r[i]) {
swap(a[i], a[r[i]]);
}
for(int i = 1; i < len; i <<= 1) {
int wn = qpow(3, (mod - 1) / (i << 1));
for(int p = i << 1, j = 0; j < len; j += p) {
int w = 1;
for(int k = 0; k < i; k++, w = 1ll * w * wn % mod) {
int x = a[j + k], y = 1ll * w * a[j + k + i] % mod;
a[j + k] = x + y;
if(a[j + k] >= mod) {
a[j + k] -= mod;
}
a[j + k + i] = x - y;
if(a[j + k + i] < 0) {
a[j + k + i] += mod;
}
}
}
}
if(f == -1) {
reverse(a + 1, a + len);
int inv = qpow(len, mod - 2);
for(int i = 0; i < len; i++) {
a[i] = 1ll * a[i] * inv % mod;
}
}
}
int C(int n, int m) {
return 1ll * fac[n] * ifac[m] % mod * ifac[n - m] % mod;
}
int main() {
#ifndef ONLINE_JUDGE
freopen("a.in", "r", stdin);
freopen("a.out", "w", stdout);
#endif
cin >> d >> n >> m;
fac[0] = ib[0] = 1;
for(int i = 1; i <= d; i++) {
fac[i] = 1ll * fac[i - 1] * i % mod, ib[i] = 1ll * ib[i - 1] * ((mod + 1) >> 1) % mod;
}
ifac[d] = qpow(fac[d], mod - 2);
for(int i = d; i; i--) {
ifac[i - 1] = 1ll * ifac[i] * i % mod;
}
while(len <= d + d) {
len <<= 1, ++l;
}
for(int i = 0; i < len; i++) {
r[i] = (r[i >> 1] >> 1) | ((i & 1) << (l - 1));
}
for(int i = 0; i <= d; i++) {
f[i] = ifac[i], g[i] = 1ll * ifac[i] * qpow(d - 2 * i + mod, n) % mod * ((i & 1) ? mod - 1 : 1) % mod;
}
NTT(f, 1), NTT(g, 1);
for(int i = 0; i < len; i++) {
f[i] = 1ll * f[i] * g[i] % mod;
}
NTT(f, -1);
for(int i = 0; i <= d; i++) {
f[i] = 1ll * f[i] * C(d, i) % mod * fac[i] % mod * ib[i] % mod;
}
memset(g, 0, sizeof(g));
for(int i = 0; i <= d; i++) {
g[i] = 1ll * fac[d - i] * f[d - i] % mod;
}
memset(f, 0, sizeof(f));
for(int i = 0; i <= d; i++) {
f[i] = 1ll * ((i & 1) ? mod - 1 : 1) * ifac[i] % mod;
}
NTT(f, 1), NTT(g, 1);
for(int i = 0; i < len; i++) {
f[i] = 1ll * f[i] * g[i] % mod;
}
NTT(f, -1);
int ans = 0;
for(int i = 0; i <= n - 2 * m && i <= d; i++) {
ans = (ans + 1ll * f[d - i] * ifac[i]) % mod;
}
cout << (ans + mod) % mod;
return 0;
}
d1t3
垃圾提答,咕咕咕。
d2t1
有哪位神仙会可以教教我啊qwq。
d2t2
一些定义:设\(t...\)为无限多个\(t\)组成的串,\(t...t\)为足够多个\(t\)组成的串。
初步的想法是,考虑字符串字典序的比较方式。对于字符串\(s,t\),从\(i=1\)开始,如果\(s_i<t_i\)那么\(s<t\),如果\(s_i>t_i\)那么\(s>t\),如果\(s_i=t_i\)那么令\(i\)增加\(1\),继续求解。
原问题首先可以容斥,考虑计算\(t...\)中不存在一个\(<s\)的子串的串\(t\)个数。可以发现,对于\(t...\)的每个长度为\(n\)的子串\(x\),假设\(x\)和\(s\)的lcp为\(k\),那么\(x_{k+1}>s_{k+1}\)或者\(k=n\)。因为和每个子串与\(s\)的lcp有关,所以可以想到建出\(s\)的kmp自动机(每个点补齐\(26\)条出边),考虑对于一个字符串\(t\),怎样根据在\(t...\)在\(s\)的kmp自动机上匹配的过程判断\(t\)是否是一个合法串。
结论是:对于kmp自动机上的每个点,令它的所有非指回根节点的出边中最大字符为\(c\),那么设它的所有\(\ge c\)的出边为合法边。字符串\(t\)合法当且仅当\(t...\)在kmp自动机上匹配时只经过合法的边。
证明:必要性显然。充分性的话,考虑假设\(t...\)中存在一个子串\(x\)满足\(x<s\),假设\(x\)与\(s\)的lcp为\(k\),那么\(k<n\)且\(x_{k+1}<s_{k+1}\)。显然\(s_k\)这个前缀和所有存在\(s_k\)这个border的前缀中一定有\(s_{k+1}\)这条非指回根的出边,那么\(t...\)在匹配\(x_{k+1}\)这个字符时走了不合法的边,产生矛盾。
有了上述结论,考虑怎样计算答案。(不知道怎么想到的)对于字符串\(t...t\),假设它最终匹配到的点为\(u\),那么在它后面(其实也相当于在前面)加入一个串\(t\)以后,它匹配到的点还是\(u\)。证明其实很显然,字符串\(t...t\)匹配到的点一定是 一个\(s\)的极长前缀,满足它是\(t...t\)的后缀 代表的点,显然在\(t...t\)前面加入一个\(t\)不会影响匹配到的点。所以考虑枚举\(t...\)最终匹配到的点\(u\),那么我们相当于要计算从\(u\)出发,只经过合法的边,回到点\(u\)且长度为\(m\)的路径条数。
可以发现,kmp自动机合法的边中去掉所有返回根的边,剩下的边会形成一个rho的结构。那么计算答案时可以分为两种情况:
1.路径不经过根。这种情况下一定是在rho中的环上绕圈,路径唯一,那么求出的环长\(len\),如果\(m\)是\(len\)的倍数那么答案加上\(len\)即可。
2.路径经过根。直接DP出\(f_{i,j}\)表示从根出发经过\(i\)步到达\(j\)的方案数和\(g_{i,j}\)表示从\(j\)出发经过\(i\)步第一次到达根的方案数,做个卷积就行了。
时间复杂度\(O(nm)\)。
#include<bits/stdc++.h>
using namespace std;
const int N = 2e3 + 10;
const int mod = 998244353;
int m, n, nxt[N], lst[N], to[N], f[N][N], g[N][N], p = 0, cnt = 0;
char s[N];
bool vis[N];
bool dfs(int u) {
if(vis[u]) {
return p = u, 1;
}
vis[u] = 1;
if(dfs(to[u])) {
return ++cnt, u != p;
}
return 0;
}
int main() {
cin >> m >> (s + 1);
n = strlen(s + 1);
for(int i = 2; i <= n; i++) {
int j = nxt[i];
while(j && s[j + 1] != s[i]) {
j = nxt[j];
}
if(s[j + 1] == s[i]) {
nxt[i] = ++j;
}
}
for(int i = 0; i <= n; i++)
for(int j = 25; ~j; j--) {
int k = i == n ? nxt[i] : i;
while(k && s[k + 1] != j + 'a') {
k = nxt[k];
}
if(s[k + 1] == j + 'a') {
lst[i] = j;
to[i] = k + 1;
break;
}
}
f[0][0] = 1;
for(int i = 0; i < m; i++)
for(int j = 0; j <= n; j++) {
f[i + 1][to[j]] = (f[i + 1][to[j]] + f[i][j]) % mod;
f[i + 1][0] = (f[i + 1][0] + 1ll * (25 - lst[j]) * f[i][j]) % mod;
}
for(int i = 0; i <= n; i++) {
g[1][i] = 25 - lst[i];
}
for(int i = 2; i <= m; i++)
for(int j = 0; j <= n; j++) {
g[i][j] = g[i - 1][to[j]];
}
dfs(0);
int ans = 0;
if(!(m % cnt)) {
ans = cnt;
}
for(int i = 0; i <= m; i++)
for(int j = 0; j <= n; j++) {
ans = (ans + 1ll * f[i][j] * g[m - i][j]) % mod;
}
int pw = 1;
for(int i = 1; i <= m; i++) {
pw = 26ll * pw % mod;
}
cout << (pw - ans + mod) % mod;
return 0;
}
rho是一个很简洁的结构,上述算法可以继续优化。设\(f_i\)为从根出发走\(i\)步后第一次回到根的方案数,可以暴力计算。设\(F\)是\(f\)的生成函数,那么所有点走\(m\)步经过根回到自己的方案数之和是\([x^m]F'x\frac{1}{1-F}\)。多项式求逆即可。
时间复杂度\(O(n+m\log m)\)。
#include<bits/stdc++.h>
using namespace std;
const int N = 1e4 + 10;
const int mod = 998244353;
int m, n, nxt[N], lst[N], to[N], p = 0, cnt = 0, l = 0, r[N], f[N], g[N], h[N];
char s[N];
bool vis[N];
bool dfs(int u) {
if(vis[u]) {
return p = u, 1;
}
vis[u] = 1;
if(dfs(to[u])) {
return ++cnt, u != p;
}
return 0;
}
int qpow(int a, int b) {
int ret = 1;
while(b) {
if(b & 1) {
ret = 1ll * ret * a % mod;
}
a = 1ll * a * a % mod, b >>= 1;
}
return ret;
}
void NTT(int *a, int n, int f) {
l = 0;
for(int m = 1; m < n; m <<= 1) {
++l;
}
for(int i = 0; i < n; i++) {
r[i] = (r[i >> 1] >> 1) | ((i & 1) << (l - 1));
}
for(int i = 0; i < n; i++) if(i < r[i]) {
swap(a[i], a[r[i]]);
}
for(int i = 1; i < n; i <<= 1) {
int wn = qpow(3, (mod - 1) / (i << 1));
for(int p = i << 1, j = 0; j < n; j += p) {
int w = 1;
for(int k = 0; k < i; k++, w = 1ll * w * wn % mod) {
int x = a[j + k], y = 1ll * w * a[j + k + i] % mod;
a[j + k] = (x + y) % mod, a[j + k + i] = (x - y) % mod;
}
}
}
if(f == -1) {
reverse(a + 1, a + n);
int inv = qpow(n, mod - 2);
for(int i = 0; i < n; i++) {
a[i] = 1ll * a[i] * inv % mod;
}
}
}
int A[N], B[N];
void pinv(int *a, int *b, int n) {
if(n == 1) {
b[0] = 1;
return;
}
pinv(a, b, n >> 1);
for(int i = 0; i < n; i++) {
A[i] = a[i], B[i] = b[i];
}
NTT(A, n << 1, 1), NTT(B, n << 1, 1);
for(int i = 0; i < (n << 1); i++) {
A[i] = 1ll * A[i] * B[i] % mod * B[i] % mod;
}
NTT(A, n << 1, -1);
for(int i = 0; i < n; i++) {
b[i] = (1ll * b[i] + b[i] - A[i]) % mod;
}
for(int i = 0; i < (n << 1); i++) {
A[i] = B[i] = 0;
}
}
int main() {
#ifndef ONLINE_JUDGE
freopen("a.in", "r", stdin);
freopen("a.out", "w", stdout);
#endif
cin >> m >> (s + 1);
n = strlen(s + 1);
for(int i = 2; i <= n; i++) {
int j = nxt[i];
while(j && s[j + 1] != s[i]) {
j = nxt[j];
}
if(s[j + 1] == s[i]) {
nxt[i] = ++j;
}
}
for(int i = 0; i <= n; i++)
for(int j = 25; ~j; j--) {
int k = i == n ? nxt[i] : i;
while(k && s[k + 1] != j + 'a') {
k = nxt[k];
}
if(s[k + 1] == j + 'a') {
lst[i] = j;
to[i] = k + 1;
break;
}
}
dfs(0);
int ans = 0;
if(!(m % cnt)) {
ans = cnt;
}
for(int i = 0, u = 0; i < m; i++, u = to[u]) {
f[i + 1] = 25 - lst[u];
}
for(int i = 1; i <= m; i++) {
g[i] = -f[i];
}
g[0] = 1;
int len = 1;
while(len <= m) {
len <<= 1;
}
pinv(g, h, len);
for(int i = 0; i <= m; i++) {
ans = (ans + 1ll * i * f[i] % mod * h[m - i]) % mod;
}
cout << ((qpow(26, m) - ans) % mod + mod) % mod;
return 0;
}
d2t3
如果是严格外向树,那么就是d1t1。
原问题容斥掉内向边就行了。
#include<bits/stdc++.h>
using namespace std;
const int N = 1e3 + 10;
const int mod = 998244353;
int gi() {
int x = 0, o = 1;
char ch = getchar();
while((ch < '0' || ch > '9') && ch != '-') {
ch = getchar();
}
if(ch == '-') {
o = -1, ch = getchar();
}
while(ch >= '0' && ch <= '9') {
x = x * 10 + ch - '0', ch = getchar();
}
return x * o;
}
int n, a[4], to[N << 1], nxt[N << 1], h[N], tt = 1, inv[3 * N], sz[N], f[N][3 * N], g[3 * N], p[N][4];
void adde(int u, int v) {
to[++tt] = v, nxt[tt] = h[u], h[u] = tt;
to[++tt] = u, nxt[tt] = h[v], h[v] = tt;
}
int qpow(int a, int b) {
int ret = 1;
while(b) {
if(b & 1) {
ret = 1ll * ret * a % mod;
}
a = 1ll * a * a % mod, b >>= 1;
}
return ret;
}
void dfs(int u, int fa) {
sz[u] = 0;
f[u][0] = 1;
for(int i = h[u], v = to[i]; i; i = nxt[i], v = to[i])
if(v ^ fa) {
dfs(v, u);
memset(g, 0, sizeof(g));
if(i & 1) {
for(int i = 0; i <= sz[u]; i++)
for(int j = 1; j <= sz[v]; j++) {
g[i] = (g[i] + 1ll * f[u][i] * f[v][j]) % mod;
g[i + j] = (g[i + j] - 1ll * f[u][i] * f[v][j]) % mod;
}
} else {
for(int i = 0; i <= sz[u]; i++)
for(int j = 1; j <= sz[v]; j++) {
g[i + j] = (g[i + j] + 1ll * f[u][i] * f[v][j]) % mod;
}
}
sz[u] += sz[v];
memcpy(f[u], g, sizeof(g));
}
sz[u] += 3;
for(int i = sz[u]; i; i--) {
f[u][i] = 0;
for(int j = 1; j <= 3 && j <= i; j++) {
f[u][i] = (f[u][i] + 1ll * f[u][i - j] * p[u][j] % mod * j % mod * inv[i]) % mod;
}
}
}
int main() {
#ifndef ONLINE_JUDGE
freopen("a.in", "r", stdin);
freopen("a.out", "w", stdout);
#endif
cin >> n;
inv[1] = 1;
for(int i = 2; i <= 3 * n; i++) {
inv[i] = 1ll * (mod - mod / i) * inv[mod % i] % mod;
}
for(int i = 1; i <= n; i++) {
int s = 0;
for(int j = 1; j <= 3; j++) {
s += a[j] = gi();
}
for(int j = 1; j <= 3; j++) {
p[i][j] = 1ll * a[j] * qpow(s, mod - 2) % mod;
}
}
for(int i = 1, u, v; i < n; i++) {
u = gi(), v = gi(), adde(u, v);
}
dfs(1, 0);
int ans = 0;
for(int i = 1; i <= 3 * n; i++) {
ans = (ans + f[1][i]) % mod;
}
cout << (ans + mod) % mod;
return 0;
}