P5689 多叉堆

寫在前面

  • 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

組合數求法

  1. 楊輝三角,加法,能夠取模。可是這是 P2822 的組合數求法,須要用到二維數組,數組大小開不下,只能過 50% 的數據。

  2. 直接根據組合數計算公式:

\[\binom{n}{m} = \frac{n!}{m!(n - m)!} \]

直接用計算式來求組合數。預處理出階乘。等下,模意義下的乘法……要求逆元啊。線性遞推求逆元能夠參考 P3811 的題解。

這道題的除數是能夠大到 \(10^9 + 7\) 的,所以不能直接遞推預處理逆元,可是隻會用到階乘的逆元,預處理這個便可。

那麼怎麼線性遞推呢?看這裏:(爲了更好體現逆元的形式,式子中保證了分數分子上都是 \(1\)

\[\frac{1}{i!} = \frac{1}{i\cdot(i - 1)!} = \frac{1}{i}\cdot\frac{1}{(i - 1)!} \]

\[\frac{1}{(i - 1)!} = i\cdot\frac{1}{i!} \]

所以,首先根據費馬小定理推論用快速冪算出 \(\frac{1}{n!}\) 隨後倒序枚舉預處理便可。

Tips

  • 節點編號是從 \(0\) 開始的,記得初始化的時候把 \(a_0,w_0\) 也一併設爲 \(1\)。(我卡了幾天就是由於這個)
  • 注意輸入格式,別忘了強制在線。
  • 處理階乘的逆元記得要枚舉到 \(0\),否則 \(m = 0\) 的時候 \(\binom{n}{m}\) 的值是錯的。

Code

代碼可讀性仍是很高的。

#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;
}
相關文章
相關標籤/搜索