题解 P6835 【[Cnoi2020]线形生物】

tiger2005

2020-09-27 16:47:12

Solution

## 题意简述 有 `N+1` 个点,从 `i` 指向 `i+1` 形成一个链,其中还有一些返祖边。 一个生物在 `1` 号点,每次将随机选择一条边通过,求出走到 `N+1` 的步数期望。 ## 分析 ![](https://cdn.luogu.com.cn/upload/image_hosting/0yk8iqz5.png)神奇做法注意! 先推式子。假设 $f_x$ 表示 `x` 走到 `N+1`的期望步数。 $$f_x=\dfrac{\sum\limits_{(x,u)\in E}f_u+1}{\sum\limits_{(x,u)\in E}1}=\dfrac{\sum\limits_{(x,u)\in E}f_u}{\operatorname{outDegree}(x)}+1$$ 这个式子的分子中只有 `x+1` 一个数大于 `x`,可以代入 $f_{x+1}$ 的表达式算 $f_x$。 $$f_x=\dfrac{\sum\limits_{(x,u)\in E}f_u}{\operatorname{outDegree}(x)}+1=\dfrac{f_{x+1}}{\operatorname{outDegree}(x)}+\dfrac{\sum\limits_{(x,u)\in E \operatorname{and} u \leq x}f_u}{\operatorname{outDegree}(x)}+1$$ 那就可以把 $f_{x+1}$ 的表达式写成一个数组 `g`,满足$f_{x+1}=g_{const}+\sum f_i \times g_i$,然后每次把这个数组除上 $\operatorname{outDegree}(x)$,然后把返祖边对应位置加上 $\dfrac{1}{\operatorname{outDegree}(x)}$,然后常数项加上 1。但是算完之后要把值算出来了,那就只能算的时候消元了。 $f_{x+1}=g_{const}+\sum f_i \times g_i=g_{const}+\sum\limits_{i \in [1,x]}f_i \times g*i+f_{i+1}*g_{i+1}$ $(1-g_{x+1})f_{x+1}=g_{const}+\sum\limits_{i \in [1,x]}f_i \times g_i$ $f_{x+1}=\dfrac{g_{const}+\sum\limits_{i \in [1,x]}f_i \times g_i}{1-g_{x+1}}$ 然后就可以把当前点的系数消掉了! 但是这么算超时是无法避免的,那怎么办呢? 想到超时的真正原因是区间除的时候使用暴力方法,那么可以使用类似于 `tag` 的思想。对于一个数组 `s` ,可以将修改变成如下形式: ``` tag=1; divideBy(a) -> tag*=a; addNumber(a,x) -> s[a]+=x*tag; getValue(a) -> return a/tag; ``` 然后这道题就可以在 $O(n+m)$ 的复杂度内作完,就是算逆元常数有点大。 ## 代码 ```cpp #include <cstdio> #include <cstring> #include <algorithm> #include <cmath> #include <vector> using namespace std; inline int read(){ register int ret=0; char ch=getchar(); while(ch<'0') ch=getchar(); while(ch>='0') ret=(ret<<3)+(ret<<1)+ch-'0',ch=getchar(); return ret; } vector<int> To[1000010]; const long long Md=998244353; int N,M; inline long long ksm(int x,int y){ register long long ret=1,bas=x; while(y){ if(y&1) (ret*=bas)%=Md; (bas*=bas)%=Md;y>>=1; } return ret; } long long SZ[1000010],MUL=1,DIV=1; inline void add(int x,long long a){ (SZ[x]+=a*DIV%Md)%=Md; } int main(){ read();N=read();M=read(); for(int i=1,u,v;i<=M;i++){ u=read();v=read(); To[u].push_back(v); } SZ[0]=0; register long long L,P,Q; for(int i=N;i;i--){ L=To[i].size()+1; P=ksm(L,Md-2); (MUL*=P)%=Md;(DIV*=L)%=Md; for(int j=0;j<To[i].size();j++) add(To[i][j],P); add(0,1); Q=(1+Md-SZ[i]*MUL%Md)%Md; (MUL*=ksm(Q,Md-2))%=Md;(DIV*=Q)%=Md; SZ[i]=0; } printf("%lld",(SZ[0]*MUL)%Md); } ```