JZOJ 5987. 【WC2019模拟2019.1.4】仙人掌毒题
Description
Input
輸入文件cactus .in
第一行4個空格隔開的整數n,m,t,w
接下來m行,每行兩個空格隔開的整數u,v,表示m次加邊操作.
Output
輸出文件為cactus.out
輸出m行,每行一個整數,表示期望模998244353的結果.
Sample Input
輸入1:
5 5 1 1
1 2
1 3
2 3
3 4
1 5
輸入2:
5 5 0 1
1 2
1 3
2 3
3 4
1 5
輸入3:
5 5 3 1
1 2
1 3
2 3
3 4
1 5
Sample Output
輸出1:
199648875
399297745
798595486
3
199648873
輸出2:
4
3
3
2
1
輸出3:
934356719
870469080
902412899
838525260
774637621
Data Constraint
Hint
更正:題目有重邊有自環也算環(無視掉題目中第一行的定義,即只要求每條邊在至多一個簡單環中)
Solution
-
設編號是 000 的點為白點、編號是 111 的點為黑點。
-
首先要看透這道題的本質:答案等于 點數-邊數+環數 (黑白點分開計算)!!
-
具體怎么算一會再說,現在先解決問題的第一步,如何判斷讀入的邊是否能夠加入。
-
用 LCT 就很好判斷了(也能用樹鏈剖分),若沒連通直接加(把邊化成點加入);
-
若已連通且路徑上的邊有的已經被標記過了,就不能加了;
-
否則可以加,并將路徑上的邊都標記一遍(這個暴力標記,每條邊最多被標記一次)。
-
這樣的話環的大小我們都能直接算出來。
-
接著我們就能算答案了。
-
先算點數貢獻: 一個點是白點的概率其實就是 (n?1n)t(\frac{n-1}{n})^t(nn?1?)t(很關鍵),所有白點貢獻即為 n?(n?1n)tn*(\frac{n-1}{n})^tn?(nn?1?)t 。
-
黑點呢就是 1?1-1?白點概率,所有黑點:n?(1?(n?1n)t)n*(1-(\frac{n-1}{n})^t)n?(1?(nn?1?)t) ,同理。
-
再算邊數貢獻: 一條邊是白邊的概率其實就是 (n?2n)t(\frac{n-2}{n})^t(nn?2?)t ,而一條黑邊的概率的計算可以“正難則反”,相當于是 (1?x(1-x(1?x是白點)?(1?y)*(1-y)?(1?y是白點))) ,即為:1?2?(n?1n)t+(n?2n)t1-2*(\frac{n-1}{n})^t+(\frac{n-2}{n})^t1?2?(nn?1?)t+(nn?2?)t
-
最后算環數貢獻: 一個大小為 mmm 白環的概率比較好算,就是 (n?mn)t(\frac{n-m}{n})^t(nn?m?)t ,本質跟前面白點、白邊是一樣的。
-
但是一個大小為 mmm 的黑環的概率就不太好算了。下面給出兩種方法:
-
方法①:分治NTT。 這樣會碼量大而且跑得慢,不過簡單直接,本質是DP計算。
-
設 f[i]f[i]f[i] 表示大小為 iii 的環在 ttt 次操作后全黑的概率,再設一個輔助數組 g[i]g[i]g[i] 表示包含 iii 個點的集合在 ttt 次操作后全白的概率。根據前面的討論即有:g[i]=(n?in)tg[i]=(\frac{n-i}{n})^tg[i]=(nn?i?)t
-
先 O(n)O(n)O(n) 預處理出 g[i]g[i]g[i] ,再用補集轉化的思想可以求出 f[i]f[i]f[i] :
-
1 減去所有不是全黑的情況的概率就是全黑的概率 ,即:f[i]=1?∑j=0i?1(f[j]?g[i?j]?Cij)f[i]=1-\sum_{j=0}^{i-1}(f[j]*g[i-j]*C_i^j)f[i]=1?j=0∑i?1?(f[j]?g[i?j]?Cij?)
-
這里的 jjj 枚舉的是環中全黑的點的個數。因為我們需要在這 iii 個點中選出 jjj 個使其全黑,所以方案數要乘一個組合數 CijC_i^jCij?。
-
這個用分治NTT就可以 O(nlog2n)O(n\ log^2n)O(n?log2n) 求出 f[i]f[i]f[i] 了(分治時先做完左邊,再算值加到右邊)。
-
方法②:容斥。 這樣計算很快、碼量小,但沒那么好想。下面的代碼我打的是容斥做法。
-
我們發現不需要將 111 到 nnn 每個 f[i]f[i]f[i] 都算出來,只用對特定的環計算即可。
-
于是可得容斥:f[m]=∑i=0m(?1)i?Cmi?(n?in)tf[m]=\sum_{i=0}^{m}(-1)^i*C_m^i*(\frac{n-i}{n})^tf[m]=i=0∑m?(?1)i?Cmi??(nn?i?)t
-
上式中 iii 枚舉的是 該環中至少有 iii 個白點 ,正確性顯然。
-
于是我們預處理階乘、逆元,每個環就可以 O(mlogt)O(m\ log\ t)O(m?log?t) 計算其全黑概率了。
-
由于每個環只用算一次,所以這部分復雜度就是 ∑mlogt=nlogt\sum m\ log\ t=n\ log\ t∑m?log?t=n?log?t 。
-
總復雜度即為 O(nlogt+mlogn)O(n\ log\ t+m\ log\ n)O(n?log?t+m?log?n) ,其中 mlognm\ log\ nm?log?n 是用 LCT 判加邊的復雜度。
Code
#include<cstdio> #include<algorithm> #include<cctype> using namespace std; typedef long long LL; const int N=1e5+5,M=N*3,mo=998244353; int tot,top,ans; int fa[M],s[M][2],sum[M],key[M],st[M]; bool rev[M],vis[M],bz[M]; int f[N],g[N],inv[N],fs[N]; inline int read() {int X=0,w=0; char ch=0;while(!isdigit(ch)) w|=ch=='-',ch=getchar();while(isdigit(ch)) X=(X<<3)+(X<<1)+(ch^48),ch=getchar();return w?-X:X; } void write(int x) {if(x>9) write(x/10);putchar(x%10+'0'); } inline bool pd(int x) {return s[fa[x]][1]==x; } inline bool isroot(int x) {return s[fa[x]][0]^x && s[fa[x]][1]^x; } inline void reverse(int x) {if(x) swap(s[x][0],s[x][1]),rev[x]^=1; } inline void update(int x) {sum[x]=sum[s[x][0]]+sum[s[x][1]]+key[x];vis[x]=vis[s[x][0]]|vis[s[x][1]]|bz[x]; } inline void down(int x) {if(rev[x]){reverse(s[x][0]),reverse(s[x][1]);rev[x]=false;} } inline void rotate(int x) {int y=fa[x],w=pd(x);if((fa[x]=fa[y]) && !isroot(y)) s[fa[y]][pd(y)]=x;if(s[y][w]=s[x][w^1]) fa[s[y][w]]=y;s[fa[y]=x][w^1]=y;update(y); } inline void splay(int x) {for(int y=st[top=1]=x;!isroot(y);y=fa[y]) st[++top]=fa[y];while(top) down(st[top--]);for(int y;!isroot(x);rotate(x))if(!isroot(y=fa[x])) rotate(pd(x)==pd(y)?y:x);update(x); } inline void access(int x) {for(int y=0;x;x=fa[y=x]){splay(x);s[x][1]=y;update(x);} } inline void mkroot(int x) {access(x),splay(x),reverse(x); } inline void link(int x,int y) {mkroot(x),fa[x]=y; } void dfs(int x) {if(s[x][0]) dfs(s[x][0]);if(s[x][1]) dfs(s[x][1]);if(x>tot) bz[x]=true;update(x); } inline int ksm(int x,int y) {int s=1;while(y){if(y&1) s=(LL)s*x%mo;x=(LL)x*x%mo;y>>=1;}return s; } inline int C(int x,int y) {return (LL)f[x]*g[y]%mo*g[x-y]%mo; } int get(int x) {return fs[x]==x?x:fs[x]=get(fs[x]); } int main() {freopen("cactus.in","r",stdin);freopen("cactus.out","w",stdout);int n=read(),m=read(),t=read(),w=read();tot=n;f[0]=g[0]=1;for(int i=1;i<=n;i++) f[i]=(LL)f[i-1]*i%mo;g[n]=ksm(f[n],mo-2);for(int i=n-1;i;i--) g[i]=(LL)g[i+1]*(i+1)%mo;inv[0]=inv[1]=1;for(int i=2;i<=n;i++) inv[i]=(LL)(mo-mo/i)*inv[mo%i]%mo;for(int i=1;i<=n;i++) fs[i]=i;int wnode=ksm((LL)(n-1)*inv[n]%mo,t);int wedge=ksm((LL)(n-2)*inv[n]%mo,t);int bedge=(1-(LL)2*wnode%mo+mo+wedge)%mo;ans=(LL)wnode*n%mo;if(w) ans=(ans+(LL)(1-wnode+mo)*n)%mo;while(m--){int x=read(),y=read();if(get(x)^get(y)){fs[get(x)]=get(y);key[++n]=1;link(x,n);link(n,y);ans=(ans+mo-wedge)%mo;if(w) ans=(ans+mo-bedge)%mo;write(ans),putchar('\n');continue;}mkroot(x),access(y),splay(y);if(vis[y]){write(ans),putchar('\n');continue;}ans=(ans-wedge+mo)%mo;if(w) ans=(ans-bedge+mo)%mo;dfs(y);int ring=sum[y]+1;ans=(ans+ksm((LL)(tot-ring)*inv[tot]%mo,t))%mo;if(w)for(int i=0;i<=ring;i++){int ss=(LL)C(ring,i)*ksm((LL)(tot-i)*inv[tot]%mo,t)%mo;if(i&1) ss=mo-ss;ans=(ans+ss)%mo;}write(ans),putchar('\n');}return 0; }總結
以上是生活随笔為你收集整理的JZOJ 5987. 【WC2019模拟2019.1.4】仙人掌毒题的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: JZOJ 5107. 【GDSOI201
- 下一篇: 多项式的求逆、取模和多点求值学习小记