CTS2019填坑

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;
}

转载于:https://www.cnblogs.com/gczdajuruo/p/10902012.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值