嘘~ 正在从服务器偷取页面 . . .

CF913F Strongly Connected Tournament 题解


CF913F Strongly Connected Tournament 题解

题目链接:CF913F Strongly Connected Tournament

题意

\(n\) 个人,编号为 \(1, 2, \cdots, n\) ,他们想要通过打架的方式决定他们的排名。

具体地,对于 \(i < j\)\(i\) 获胜的概率恒为 \(p\)\(p\) 通过给你两个非负整数 \(A, B~(A \leq B),~p = \frac{A}{B}\) 的方式给出。

初始时,所有人都还没决出胜负。每一轮,对还未决出胜负的每一对人进行打架,得到一张竞赛图。将其进行缩点后,得到若干个 SCC(强连通分量)。

此时,如果两个人处在不同的 SCC 中,则它们已经决出胜负。否则,它们还未决出胜负 (下一轮还要继续)。

如果某一时刻所有人都决出胜负,则比赛结束,否则进行下一轮比赛。

你需要求出,这 \(n\) 个人的比赛中打架次数的期望。

输入格式

第一行包含一个正整数 \(n~(n\le 3000)\),表示总人数。

第二行包含两个非负整数 \(A, B~(0 \le A \le 100,B \neq 0)\) ,表示概率为 \(p = \frac{A}{B}\)

输出格式

输出一行一个整数,表示打架次数的期望在模 \(998244353\) 意义下的结果。

综合性很强的dp题。

注意到每一轮比赛其实就相当于给无向完全图定向。

不难发现定向后一定是一张竞赛图。众所周知竞赛图缩点后为呈单向链状,每个点向其后所有点连边

因此我们可以根据套路(防止重复计算贡献),考虑拓扑序最靠前的那个 SCC(就是缩点后的点)。

具体地,设 \(E_n\) 表示 \(n\) 个点的无向完全图期望的打架次数, \(F_n\) 表示 \(n\) 个点的带权随机竞赛图的期望打架次数

这里的带权随机竞赛图指的就是 \(p\) 概率定向为 \(u\to v\)\(q=1-p\) 概率定向为 \(v \to u\) 的竞赛图

注意到一张无向完全图在一轮比赛后会增加 \(\binom{n}{2}\) 次打架,然后转化为一张带权随机竞赛图,则 \[ E_n = F_n + \dbinom{n}{2}\tag{1} \label{1} \] 考虑 \(F_n\) 的转移。记刚刚提到的那个「拓扑序最靠前的那个 SCC」为 \(S\) ,并记 \(k = |S|\)

显然 \(S\) 中的所有点构成了一个 SCC ,并且容易发现带权随机竞赛图的强连通性仅与其点数有关

于是设 \(n\) 个点的带权随机竞赛图强连通的概率\(C_n\)

同时,注意到任意 \(S\) 中的点与 \(V \setminus S\) 中的点的连边一定为 \(S\) 指向 \(V \setminus S\) ,即 \[ \forall u \in S, ~\forall v \in (V \setminus S),~\exists!( u \to v) \tag{2}\label{2} \] 记这个概率为 \(\mathtt{cross}(S,~V\setminus S)\)

接着考虑 \(V \setminus S\) 内部的连边,显然只与 \(|V \setminus S|\) 有关,则它的打架次数期望应为 \(F_{|V\setminus S|}\) \[ F_i=\sum_{S \subseteq V \land S \neq \varnothing} C_{|S|} \cdot \mathtt{cross}\left(S, ~V\setminus S\right) \cdot\left(E_{|S|}+F_{|V \setminus S|}\right) \tag{3} \label{3} \] 注意到这里只有 \(\mathtt{cross}(S,~V\setminus S)\)\(S\) 的具体构造有关,而其它项均与 \(|S|\) 有关。

因此如果我们求出了以下式子 \[ \mathtt{cro}(k,~|V| - k) = \sum_{S \subseteq V \land |S| = k} \mathtt{cross}(S,~V\setminus S) \tag{4}\label{4} \] 则原转移方程就转化为了仅与 \(|S|\) 有关的式子,即 \[ F_i = \sum_{k = 1}^{i} C_k \cdot \mathtt{cro}(k,i-k)\cdot(E_k + F_{i-k}) \tag{5}\label{5} \]\(\eqref{1}\) 代入 \(\eqref {5}\) ,就能做到 \(\mathcal{O}(n^2)\)\(F_n\) 了。

考虑如何求解 \(\mathtt{cro}(n,m)\)\(C_n\)

首先 \(C_n\) 是一个经典的容斥,考虑不强连通的情况,用 \(1\) 减掉就好了(这里用 \(\subset\) 表示真子集) \[ C_i = 1 - \sum_{S\subset V \land S \ne \varnothing}C_{|S|} \,\cdot\,\mathtt{cross}(S,~V\setminus S) \tag{6} \label{6} \] 也就是(拜拜容斥 qwq\[ C_i = \sum_{k=1}^{i-1}C_k \cdot \mathtt{cro}(k,i-k) \tag{7} \label{7} \] 也可以在 \(\mathcal{O}(n^2)\) 的时间内求出。最后就是 \(\mathtt{cro}(n,m)\) 了,定义见 \(\eqref{4}\)

显然一共有 \(\binom{n+m}{n}\) 种情况,考虑其中最大的元素 \(n+m\) 是属于 \(S\) 还是属于 \(V \setminus S\)

对于 \(n+m\) 属于 \(S\)\(\binom{n+m-1}{n-1}\) 种情况,与 \(m\) 关联的边中,每条边方向正确的概率为 \(q\) ,则概率为 \(q^m\)

对于 \(n+m\) 属于 \(V \setminus S\)\(\binom{n+m-1}{m-1}\) 种情况,与 \(m\) 关联的边中,每条边方向正确的概率为 \(p\) ,则概率为 \(p^n\)

则有 \[ \mathtt{cro}(n,m) = p^n\cdot\mathtt{cro}(n,m-1) + q^m\cdot \mathtt{cro}(n-1,m)\tag{8}\label{8} \] 边界: \(\mathtt{cro}(n,0) = \mathtt{cro}(0,n) = 1\)

然后这东西是有个封闭形式的。记 \[ u_1=1,~u_n = p^{n-1} + p^{n-2}q + \cdots + pq^{n-2} + q^{n-1} \tag{9} \]\(U_n = \prod_{1 \le i \le n}u_i\) ,则有 \[ \mathtt{cro}(n,m) = \frac{U_{n+m}}{U_nU_m} \tag{10} \] 证明用归纳法就可以了,不过怎么推出来的我也不清楚

时间复杂度 \(\mathcal{O}(n^2)\)

代码:

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f
#define inv(x) (qpow((x), mod-2))
const int mod = 998244353, inv2 = (mod + 1) / 2;
void up(int &x, const int y) { x < y ? x = y : 0; }
void down(int &x, const int y) { x > y ? x = y : 0; }
void add(int &x, const int y) { (x += y) >= mod ? x -= mod : 0; }
int &Mod(int &x) { return x = (x < 0 ? (x % mod + mod) : (x % mod));}
#define N ((int)(3e3+150))

int n,A,B,p,q,pn,qn,pq,c=1;
int P[N],fy[N],fi[N],con[N],E[N],C[N];
int mul(int cnt, ...)
{
    va_list ptr; va_start(ptr,cnt); int res = 1;
    for(int i=0; i<cnt; i++) res = res * va_arg(ptr,int) % mod;
    va_end(ptr); return res;
}
int qpow(int a,int b)
{
    int ans = 1, base = a % mod;
    for(; b; b >>= 1)
    {
        if(b & 1) ans = ans * base % mod;
        base = base * base % mod;
    }
    return ans;
}
int cro(int a,int b) { return mul(3, fy[a+b], fi[a], fi[b]); }
int C2(int x) { return x * (x-1) / 2 % mod; }
signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    // freopen("check.in","r",stdin);
    // freopen("check.out","w",stdout);
    cin >> n >> A >> B;
    pn = p = A % mod * inv(B) % mod; qn = Mod(q = 1 - p);
    if(p == q)
    {
        for(int i=1; i<=n; i++, c = c * inv2 % mod)
            P[i] = i * c % mod;
    }else
    {
        pq = inv(p-q);
        for(int i=1; i<=n; i++, pn = pn * p % mod, qn = qn * q % mod)
            Mod(P[i] = (pn - qn) * pq % mod);
    }
    fy[0] = 1; for(int i=1; i<=n; i++) fy[i] = fy[i-1] * P[i] % mod;
    fi[n] = inv(fy[n]); for(int i=n; i; i--) fi[i-1] = fi[i] * P[i] % mod;
    con[1] = 1;
    for(int i=3,j; i<=n; Mod(con[i++]))
        for(con[i] = j = 1; j < i; j++)
            con[i] = (con[i] - con[j]*cro(j,i-j)) % mod;
    E[2] = C[2] = 1;
    for(int i=3,j; i<=n; i++)
    {
        for(c = C[i] = C2(i), j=1; j < i; j++)
            add(c, mul(3, cro(j,i-j), con[j], E[j] + E[i-j] - C[i-j]));
        Mod(E[i] = (c * inv(1-con[i]) % mod));
    }
    cout << E[n] << '\n';
    return 0;
}

参考文献

[1] https://yhx-12243.github.io/OI-transit/?redirect=545


文章作者: q779
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-ND 4.0 许可协议。转载请注明来源 q779 !
评论
  目录