OI 生涯中 AC 的首道組合數學應用題。c++
開題 5min 發現規律,寫了半下午代碼,調了兩天,然而甚至沒過樣例,心態崩了。幾天以後從新寫了一份代碼才 AC。算法
雖然思惟難度不大,但畢竟是聯賽題,題目質量仍是很高的。涉及到了不少組合數學的基礎算法,寫完以後感受學到了不少。感受這道題是道不錯的組合數學入門題。接下來我將會盡可能詳細地進行講解。數組
只有根節點的答案有用。任何一個節點在更新完其父親結點的值後,其自己的任何值將不會再有任何改動或貢獻,所以用並查集維護便可,記得路徑壓縮。數據結構
解決這道題的關鍵,在於鏈接兩個節點時答案的更新和與答案相關的值的維護。spa
設 \(a_i\) 表示以 \(i\) 爲根節點的子樹的填數方案總數。code
假設當前鏈接的兩個節點的編號分別是 \(u,v\)(保證兩個節點都是根節點),且本次操做須要從 \(u\) 接到 \(v\) 上去。ip
比較顯而易見的一點是,更新答案時,節點 \(v\) 所處的位置,必定只能填 \(0\)。get
維護 \(w_i\) 表示以 \(i\) 爲根的樹的重量,即節點數。將 \(u\) 鏈接到 \(v\) 上時,將 \(w_v\) 的值加上 \(w_u\)。數學
那麼填入原來的 \(u\) 子樹中的數字就有 \(w_v - 1\) 選 \(w_u\) 種不一樣的選擇方案。剩下的數字填入原來的 \(v\) 子樹中。又本來 \(u,v\) 子樹中的填數總方案數分別爲 \(a_u,a_v\)。那麼根據乘法原理,就能夠將 \(a_v\) 更新爲 \(a_v \times a_u \times \binom{n}{m}\)it
楊輝三角,加法,能夠取模。可是這是 P2822 的組合數求法,須要用到二維數組,數組大小開不下,只能過 50% 的數據。
直接根據組合數計算公式:
直接用計算式來求組合數。預處理出階乘。等下,模意義下的乘法……要求逆元啊。線性遞推求逆元能夠參考 P3811 的題解。
這道題的除數是能夠大到 \(10^9 + 7\) 的,所以不能直接遞推預處理逆元,可是隻會用到階乘的逆元,預處理這個便可。
那麼怎麼線性遞推呢?看這裏:(爲了更好體現逆元的形式,式子中保證了分數分子上都是 \(1\))
所以,首先根據費馬小定理推論用快速冪算出 \(\frac{1}{n!}\) 隨後倒序枚舉預處理便可。
代碼可讀性仍是很高的。
#include<bits/stdc++.h> #define LL long long using namespace std; const int Maxn = 3e5 + 5; const LL Mod = 1e9 + 7; int n, q, opt, x, y; int Ans; LL a[Maxn] = {1}; int w[Maxn] = {1}; /*快速冪*/ inline LL qpow(LL b, LL p) { if(p == 0) return 1; LL x = 1; for(; p;b *= b, b %= Mod,p >>= 1) if(p & 1) x *= b, x %= Mod; return x; } /*並查集*/ int fa[Maxn]; int find(int t) { return fa[t] == t ? t : fa[t] = find(fa[t]); } /*數學*/ LL fac[Maxn] = {1}; LL invf[Maxn]; LL C(int N, int M) { return 1ll * fac[N] % Mod * invf[M] % Mod * invf[N - M] % Mod; } /*預處理*/ void Setup() { for(register int i = 1; i <= n; ++i) { fa[i] = i; w[i] = a[i] = 1; fac[i] = fac[i - 1] * i; fac[i] %= Mod; } invf[n] = qpow(fac[n], Mod - 2); for(register int i = n - 1; i >= 0; --i) { invf[i] = invf[i + 1] * (i + 1); invf[i] %= Mod; } } /*連接 處理答案*/ void line(int u, int v) { w[v] += w[u]; a[v] = a[v] * a[u] % Mod * C(w[v] - 1, w[u]) % Mod; fa[u] = v; } /*輸出答案 更新強制在線值*/ void print(int t) { Ans = (int)a[t]; printf("%d\n", Ans); } /*快速讀入*/ inline int read() { int f = 1, w = 0; char ch = getchar(); for(; (ch < '0') || (ch > '9'); ch = getchar()) if(ch == '-') f = -1; for(; (ch >= '0') && (ch <= '9'); ch = getchar()) w = (w << 3) + (w << 1) + (int)(ch ^ '0'); return f * w; } int main() { n = read(); q = read(); Setup(); while(q--) { opt = read(); if(opt == 1) { x = (read() + Ans) % n; y = (read() + Ans) % n; x = find(x); y = find(y); line(x, y); } else { x = (read() + Ans) % n; x = find(x); print(x); } } return 0; }