數學小結(持續更新)

ez_lcw發表於2020-11-06

數學小結

前言:這篇文章專門整理了各種有關數學的東西(包括原理和例題),主要是怕自己忘了。

整理的不齊全,而且寫的都是比較基礎的東西。

擴充套件歐幾里得(exgcd)

問方程 a x + b y = c ax+by=c ax+by=c x x x 的最小非負整數解。

根據貝祖定理,若 gcd ⁡ ( a , b ) ∣ c \gcd(a,b)|c gcd(a,b)c,則方程有解,否則無解。

那麼我們先可以求出 a x + b y = gcd ⁡ ( a , b ) ax+by=\gcd(a,b) ax+by=gcd(a,b) 的一組解,然後 x x x y y y 同時乘上 c gcd ⁡ ( a , b ) \dfrac{c}{\gcd(a,b)} gcd(a,b)c,就可以得到 a x + b y = c ax+by=c ax+by=c 的解了。至於怎麼求 x x x 的最小非負整數解,我們先要將原方程約分,即 a a a b b b c c c 都除以 gcd ⁡ ( a , b ) \gcd(a,b) gcd(a,b),然後再根據不定方程,讓 x x x 模上 b b b 就好了。

現在的問題就是如何求出 a x + b y = gcd ⁡ ( a , b ) ax+by=\gcd(a,b) ax+by=gcd(a,b) 的一組解。

考慮用類似求 gcd ⁡ \gcd gcd 的輾轉相除法求解。

假設我們已經求出了 a ′ x ′ + b ′ y ′ = gcd ⁡ ( a ′ , b ′ ) a'x'+b'y'=\gcd(a',b') ax+by=gcd(a,b) 的一組解,其中 a ′ = b a'=b a=b b ′ = a   m o d   b = a − ⌊ a b ⌋ × b b'=a\bmod b=a-\lfloor\dfrac{a}{b}\rfloor\times b b=amodb=aba×b,現在考慮如何求 x x x y y y 的一組解。

顯然,根據輾轉相除法, gcd ⁡ ( a , b ) = gcd ⁡ ( b , a   m o d   b ) = gcd ⁡ ( a ′ , b ′ ) \gcd(a,b)=\gcd(b,a\bmod b)=\gcd(a',b') gcd(a,b)=gcd(b,amodb)=gcd(a,b)

那麼有:

a ′ x ′ + b ′ y ′ = gcd ⁡ ( a ′ , b ′ ) b x ′ + ( a − ⌊ a b ⌋ × b ) y ′ = gcd ⁡ ( a , b ) a y ′ + b ( x ′ − ⌊ a b ⌋ × y ′ ) = gcd ⁡ ( a , b ) \begin{aligned} a'x'+b'y'&=\gcd(a',b')\\ bx'+\left(a-\lfloor\dfrac{a}{b}\rfloor\times b\right)y'&=\gcd(a,b)\\ ay'+b\left(x'-\lfloor\dfrac{a}{b}\rfloor\times y'\right)&=\gcd(a,b) \end{aligned} ax+bybx+(aba×b)yay+b(xba×y)=gcd(a,b)=gcd(a,b)=gcd(a,b)

可以推得:

{ x = y ′ y = x ′ − ⌊ a b ⌋ × y ′ \begin{cases} x=y'\\ y=x'-\lfloor\dfrac{a}{b}\rfloor\times y' \end{cases} {x=yy=xba×y

這樣就可以求出 x x x y y y 的一組解了。

考慮邊界情況:當 b = 0 b=0 b=0 時,有 a x = gcd ⁡ ( a , b ) = a ax=\gcd(a,b)=a ax=gcd(a,b)=a,得 x = 1 x=1 x=1 y = 0 y=0 y=0

然後根據一開始說的方法就能求出 x x x 的最小非負整數解了。

程式碼如下:

void exgcd(ll a,ll b,ll &x,ll &y)
{
	if(!b)
	{
		x=1,y=0;
		return;
	}
	exgcd(b,a%b,x,y);
	ll tmp=x;
	x=y;
	y=tmp-a/b*y;
}

求逆元

a × x ≡ 1 ( m o d p ) a\times x\equiv 1 \pmod p a×x1(modp),則稱 x x x a a a 在模 p p p 意義下的逆元,記作 a − 1 a^{-1} a1,通常來算 1 a   m o d   p \dfrac{1}{a}\bmod p a1modp 的值。(保證 gcd ⁡ ( a , p ) = 1 \gcd(a,p)=1 gcd(a,p)=1

  1. p ∈ p r i m e p\in prime pprime,根據費馬小定理, a p − 1 ≡ 1 ( m o d p ) a^{p-1}\equiv 1\pmod p ap11(modp),即 a × a p − 2 ≡ 1 ( m o d p ) a\times a^{p-2}\equiv 1 \pmod p a×ap21(modp),那麼答案即為 a p − 2   m o d   p a^{p-2}\bmod p ap2modp

  2. p ∉ p r i m e p\not\in prime pprime,那麼 a × x ≡ 1 ( m o d p ) a\times x\equiv 1 \pmod p a×x1(modp) 可以看做是 a x + p y = 1 ax+py=1 ax+py=1 的一組解。

程式碼就不放了。

中國剩餘定理(crt)

問以下方程的最小非負整數解:

{ x ≡ a 1 ( m o d p 1 ) x ≡ a 2 ( m o d p 2 ) ⋯ x ≡ a k ( m o d p k ) \begin{cases} x\equiv a_1 \pmod{p_{1}}\\ x\equiv a_2 \pmod{p_{2}}\\ \cdots\\ x\equiv a_k \pmod{p_{k}} \end{cases} xa1(modp1)xa2(modp2)xak(modpk)

其中 p 1 , p 2 , ⋯   , p k p_1,p_2,\cdots,p_k p1,p2,,pk 為兩兩互質的數。

而這個定理貌似就是在構造一個特殊解。

M = ∏ i = 1 k p i M=\prod\limits_{i=1}^{k}p_i M=i=1kpi m i = M p i m_i=\dfrac{M}{p_i} mi=piM t i t_i ti m i m_i mi 在模 p i p_i pi 意義下的逆元,則有 m i × t i ≡ 1 ( m o d p i ) m_i\times t_i\equiv 1 \pmod{p_i} mi×ti1(modpi)

那麼這個方程的一個特殊解就是: x 0 = ∑ i = 1 k a i m i t i x_0=\sum\limits_{i=1}^{k}a_im_it_i x0=i=1kaimiti

證明:

對於任意的 i i i,我們考慮 x 0 ≡ a i ( m o d p i ) x_0\equiv a_i \pmod{p_{i}} x0ai(modpi) 是否成立。

x 0 x_0 x0 拆解成兩部分之和: x 0 = ∑ j = 1 , j ≠ i k a j m j t j + a i m i t i x_0=\sum\limits_{j=1,j\neq i}^{k}a_jm_jt_j+a_im_it_i x0=j=1,j=ikajmjtj+aimiti

顯然,對於任意的 j ≠ i j\neq i j=i a j m j t j   m o d   p i a_jm_jt_j\bmod p_i ajmjtjmodpi 肯定為 0 0 0,因為 m j m_j mj 的定義就是除了 p j p_j pj 之外的 p i p_i pi 之積,得 m j   m o d   p i m_j\bmod p_i mjmodpi 0 0 0

那麼就可以得到 ∑ j = 1 , j ≠ i k a j m j t j ≡ 0 ( m o d p i ) \sum\limits_{j=1,j\neq i}^{k}a_jm_jt_j\equiv 0 \pmod{p_i} j=1,j=ikajmjtj0(modpi)

然後因為 m i t i ≡ 1 ( m o d p i ) m_it_i\equiv 1 \pmod{p_i} miti1(modpi),得 a i m i t i ≡ a i ( m o d p i ) a_im_it_i\equiv a_i \pmod{p_i} aimitiai(modpi)

所以 x 0 = ∑ j = 1 , j ≠ i k a j m j t j + a i m i t i ≡ a i ( m o d p i ) x_0=\sum\limits_{j=1,j\neq i}^{k}a_jm_jt_j+a_im_it_i\equiv a_i \pmod{p_{i}} x0=j=1,j=ikajmjtj+aimitiai(modpi)

那麼 x 0 x_0 x0 就是 x x x 的一個特殊解了。

證畢。

然後又因為 x x x 一定是 a M + b aM+b aM+b 的形式,所以 x min ⁡ = x 0   m o d   M x_{\min}=x_0\bmod M xmin=x0modM

程式碼就不放了。

擴充套件中國剩餘定理(excrt)

問以下方程的最小非負整數解:

{ x ≡ a 1 ( m o d p 1 ) x ≡ a 2 ( m o d p 2 ) ⋯ x ≡ a k ( m o d p k ) \begin{cases} x\equiv a_1 \pmod{p_{1}}\\ x\equiv a_2 \pmod{p_{2}}\\ \cdots\\ x\equiv a_k \pmod{p_{k}} \end{cases} xa1(modp1)xa2(modp2)xak(modpk)

發現這類問題和普通中國剩餘定理的區別就是少了這個條件:“其中 p 1 , p 2 , ⋯   , p k p_1,p_2,\cdots,p_k p1,p2,,pk 為兩兩互質的數”。

這就導致了 m i m_i mi 在模 p i p_i pi 意義下的逆元可能根本不存在,所以不能那麼做。

假設前 i i i 個方程組的特殊解已經求出來為 x 0 x_0 x0

M i = ∏ j = 1 i p j M_i=\prod\limits_{j=1}^{i}p_j Mi=j=1ipj

那麼前 i i i 個方程組的通解為 x i = x 0 + M i × t x_i=x_0+M_i\times t xi=x0+Mi×t t ∈ Z t\in Z tZ)。

我們現在就是要找到一個 t t t,使得 x 0 + M i × t ≡ a i ( m o d p i ) x_0+M_i\times t\equiv a_i\pmod{p_{i}} x0+Mi×tai(modpi),即 M i × t ≡ a i − x 0 ( m o d p i ) M_i\times t\equiv a_i-x_0\pmod{p_{i}} Mi×taix0(modpi)

這個用 exgcd 來做就可以了。

然後這麼一直下去就能得到最後的解了。

程式碼如下:

//這裡的m陣列就是p陣列

ll exgcd(ll a,ll b,ll &x,ll &y)
{
	if(!b)
	{
		x=1,y=0;
		return a;
	}
	ll gcd=exgcd(b,a%b,x,y);
	ll xx=x;
	x=y;
	y=xx-a/b*y;
	return gcd;
}

ll excrt()
{
	ll M=m[1],ans=aa[1];
	for(int i=2;i<=n;i++)
	{
		ll a=M,b=m[i],c=((aa[i]-ans)%b+b)%b,x,y;
		ll gcd=exgcd(a,b,x,y);
		if(c%gcd)
			return -1;
		ll mod=b/gcd;
		x=mul(x,c/gcd,mod);
		ans+=x*M;
		M*=mod;
		ans=(ans%M+M)%M;
	}
	return ans;
}

以上都應該是初一時應該學會的,結果沒學好,又複習了一遍……

下面的就是最近才學的。


盧卡斯定理(Lucas)

原理

( n m )   m o d   p \dbinom{n}{m}\bmod p (mn)modp 的值,其中 p ∈ p r i m e p\in prime pprime

n = a p + b n=ap+b n=ap+b m = l p + r m=lp+r m=lp+r

那麼 ( n m ) \dbinom{n}{m} (mn) 就相當於 ( x + 1 ) n (x+1)^n (x+1)n 的展開式中 x m x^m xm 的係數。

然後先證明一個東西: ( x + 1 ) p ≡ x p + 1 ( m o d p ) (x+1)^p\equiv x^p+1\pmod p (x+1)pxp+1(modp)

證明比較簡單,把 ( x + 1 ) p (x+1)^p (x+1)p 展開,得 ( x + 1 ) p ≡ ∑ i = 0 p ( p i ) x i ( m o d p ) (x+1)^p\equiv \sum\limits_{i=0}^{p}\dbinom{p}{i}x^i\pmod p (x+1)pi=0p(ip)xi(modp),其中對於任意的 0 < i < p 0<i<p 0<i<p,肯定有 ( p i ) = p × ( p − 1 ) × ⋯ × ( p − i + 1 ) 1 × 2 × ⋯ × i \dbinom{p}{i}=\dfrac{p\times (p-1)\times \cdots\times(p-i+1)}{1\times 2\times \cdots\times i} (ip)=1×2××ip×(p1)××(pi+1) p p p 的倍數,因為 p p p 為質數, 1 ∼ i 1\sim i 1i 均與 p p p 互質。

證畢。

然後推式子:

( x + 1 ) n ≡ ( x + 1 ) a p + b ≡ ( x + 1 ) a p × ( x + 1 ) b ≡ ( ( x + 1 ) p ) a × ( x + 1 ) b ≡ ( x p + 1 ) a × ( x + 1 ) b ≡ ( ∑ i = 1 a ( a i ) x i p ) × ( ∑ j = 1 b ( b j ) x j ) ( m o d p ) \begin{aligned} &(x+1)^n\\ \equiv&(x+1)^{ap+b}\\ \equiv&(x+1)^{ap}\times(x+1)^b\\ \equiv&((x+1)^p)^a\times (x+1)^b\\ \equiv&(x^p+1)^a\times(x+1)^b\\ \equiv&\left(\sum\limits_{i=1}^{a}\binom{a}{i}x^{ip}\right)\times\left(\sum_{j=1}^{b}\binom{b}{j}x^j\right)\pmod p \end{aligned} (x+1)n(x+1)ap+b(x+1)ap×(x+1)b((x+1)p)a×(x+1)b(xp+1)a×(x+1)b(i=1a(ia)xip)×(j=1b(jb)xj)(modp)

然後考慮如何找到這個式子中的第 m m m 次項。

m = l p + r m=lp+r m=lp+r

由於 1 ≤ j ≤ b ≤ p 1\leq j\leq b\leq p 1jbp,所以 x m = x l p + r x^m=x^{lp+r} xm=xlp+r 只能是由第一個括號裡面的 x l p x^{lp} xlp 和第二個括號裡面的 x r x^r xr 拼湊而來,故 x m x^m xm 的係數為 ( a l ) × ( b r ) \dbinom{a}{l}\times\dbinom{b}{r} (la)×(rb)

又因為 x m x^m xm 本來的係數就是 ( n m ) \dbinom{n}{m} (mn),所以得:

( n m ) = ( a l ) × ( b r ) = ( ⌊ n p ⌋ ⌊ m p ⌋ ) × ( n   m o d   p m   m o d   p ) \dbinom{n}{m}=\dbinom{a}{l}\times\dbinom{b}{r}=\dbinom{\lfloor\frac{n}{p}\rfloor}{\lfloor\frac{m}{p}\rfloor}\times\dbinom{n\bmod p}{m\bmod p} (mn)=(la)×(rb)=(pmpn)×(mmodpnmodp)

然後就可以遞迴求解了。

程式碼如下:

ll Lucas(ll n,ll m)
{
	if(!m) return 1;
	return Lucas(n/p,m/p)*C(n%p,m%p)%p;
}

至於怎麼求 ( n m ) ( m o d p ) \dbinom{n}{m}\pmod p (mn)(modp)(其中 0 ≤ m ≤ n < p 0\leq m \leq n<p 0mn<p),這裡介紹以下幾種方法:

  1. 通過楊輝三角 O ( p 2 ) O(p^2) O(p2) 預處理 C 陣列,詢問 O ( 1 ) O(1) O(1)

  2. 用公式 ( n m ) = n ! ( n − m ) ! m ! \dbinom{n}{m}=\dfrac{n!}{(n-m)!m!} (mn)=(nm)!m!n! O ( p ) O(p) O(p) 求。

    程式碼如下:

     ll poww(ll a,ll b)
     {
     	ll ans=1;
     	while(b)
     	{
     		if(b&1) ans=(ans*a)%p;
     		a=(a*a)%p;
     		b>>=1;
     	}
     	return ans;
     }
    
     ll C(ll n,ll m)
     {
     	if(m>n) return 0;
     	m=min(m,n-m);
     	ll a=1,b=1;
     	for(int i=1;i<=m;i++)
     	{
     		a=(a*(n-i+1))%p;
     		b=(b*i)%p;
     	}
     	return a*poww(b,p-2)%p;
     }
    
  3. O ( p ) O(p) O(p) 預處理階乘陣列 fac和階乘的逆元陣列 inv,詢問 O ( 1 ) O(1) O(1)

例題

[ZJOI2010]排列計數

看到 p i > p ⌊ i 2 ⌋ p_i>p_{\lfloor\frac{i}{2}\rfloor} pi>p2i,聯想到二叉樹結構。

那麼這就是一個結構固定的二叉樹,需要滿足子節點權值大於父節點權值。

類似一個小根堆。

f i f_i fi 表示以 i i i 為根的子樹的方案數。顯然這棵樹裡面有 s i z e i size_i sizei 個權值。

那麼 i i i 節點的權值是這些權值裡面最小的一個。

然後剩下的 s i z e i − 1 size_i-1 sizei1 個權值分給左兒子 s i z e l c size_{lc} sizelc 個,右兒子 s i z e r c size_{rc} sizerc 個。

就有狀態轉移方程: f i = ( s i z e i − 1 s i z e l c ) × f l c × f r c f_i=\dbinom{size_i-1}{size_{lc}}\times f_{lc}\times f_{rc} fi=(sizelcsizei1)×flc×frc

程式碼如下:

#include<bits/stdc++.h>

#define lc (u<<1)
#define rc (u<<1|1)
#define N 2000010
#define ll long long

using namespace std;

ll n,p,f[N],fac[N];
int size[N];

ll poww(ll a,ll b)
{
	ll ans=1;
	while(b)
	{
		if(b&1) ans=ans*a%p;
		a=a*a%p;
		b>>=1;
	}
	return ans;
}

ll C(ll n,ll m)
{
	if(n<m) return 0;
	if(!m) return 1;
	return fac[n]*poww(fac[n-m],p-2)%p*poww(fac[m],p-2)%p;
}

ll Lucas(ll n,ll m)
{
	if(n<m) return 0;
	if(!m) return 1;
	return C(n%p,m%p)*Lucas(n/p,m/p)%p;
}

void dfs(int u)
{
	if(u>n) return;
	dfs(lc),dfs(rc);
	size[u]=1+size[lc]+size[rc];
	f[u]=Lucas(size[u]-1,size[lc]);
	if(lc<=n) f[u]=(f[u]*f[lc])%p;
	if(rc<=n) f[u]=(f[u]*f[rc])%p;
}

int main()
{
	scanf("%lld%lld",&n,&p);
	fac[0]=1;
	for(int i=1;i<=n;i++)
		fac[i]=fac[i-1]*i%p;
	dfs(1);
	printf("%lld\n",f[1]);
	return 0;
}

[SHOI2015]超能粒子炮·改

奇怪的推式子題。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-vZFiEzQk-1604621224493)(http://192.168.102.138/JudgeOnline/upload/attachment/image/20180615/20180615104746_54736.png)]

程式碼如下:

#include<bits/stdc++.h>

#define N 2500
#define mod 2333
#define ll long long

using namespace std;

int t;
ll n,k,C[N][N],f[N][N];

void init()
{
	C[0][0]=1;
	for(int i=1;i<mod;i++)
	{
		C[i][0]=C[i][i]=1;
		for(int j=1;j<i;j++)
			C[i][j]=(C[i-1][j-1]+C[i-1][j])%mod;
	}
	for(int i=0;i<mod;i++)
	{
		f[i][0]=1;
		for(int j=1;j<mod;j++)
			f[i][j]=(f[i][j-1]+C[i][j])%mod;
	}
}

ll Lucas(ll n,ll m)
{
	if(!m) return 1;
	if(n<m) return 0;
	return C[n%mod][m%mod]*Lucas(n/mod,m/mod)%mod;
}

ll F(ll n,ll k)
{
	if(k<0) return 0;
	if(!n||!k) return 1;
	if(n<mod&&k<mod) return f[n][k];
	return (f[n%mod][mod-1]*F(n/mod,k/mod-1)%mod+Lucas(n/mod,k/mod)*f[n%mod][k%mod]%mod)%mod;
}

int main()
{
	init();
	scanf("%d",&t);
	while(t--)
	{
		scanf("%lld%lld",&n,&k);
		printf("%lld\n",F(n,k));
	}
	return 0;
}

[BJWC2018]上學路線

題解

Baby Step Giant Step(BSGS,北上廣深

原理

a x ≡ b ( m o d p ) a^x\equiv b \pmod p axb(modp) 的最小非負整數解,其中 p ∈ p r i m e p\in prime pprime

t = p t=\sqrt p t=p x = i t − j x=it-j x=itj。(因為 p ∈ p r i m e p\in prime pprime,所以 0 ≤ x ≤ p − 1 0\leq x\leq p-1 0xp1

a x ≡ b ( m o d p ) a i t − j ≡ b ( m o d p ) a i t ≡ b × a j ( m o d p ) \begin{aligned} a^x&\equiv b \pmod p\\ a^{it-j}&\equiv b\pmod p\\ a^{it}&\equiv b\times a^j \pmod p \end{aligned} axaitjaitb(modp)b(modp)b×aj(modp)

然後我們先可以 O ( p ) O(\sqrt p) O(p ) b × a j b\times a_j b×aj 的所有值存進 hash 表裡面。

然後我們再 O ( p ) O(\sqrt p) O(p ) 列舉 i i i,在 hash 表裡面找是否有 a i t ≡ b × a j ( m o d p ) a^{it}\equiv b\times a^j \pmod p aitb×aj(modp)

程式碼如下:

#include<bits/stdc++.h>

#define ll long long

using namespace std;

ll p,b,n,t,num,ans=-1;

map<ll,int>mp;

ll poww(ll a,ll b)
{
	ll ans=1;
	while(b)
	{
		if(b&1) ans=ans*a%p;
		a=a*a%p;
		b>>=1;
	}
	return ans;
}

int main()
{
	scanf("%lld%lld%lld",&p,&b,&n);
	num=t=sqrt(p);
	if(num*num<p) num++;
	for(int i=0;i<t;i++)
		mp[n*poww(b,i)%p]=i;
	for(int i=1;i<=num;i++)
	{
		ll tmp=poww(b,t*i);
		if(mp[tmp])
		{
			ans=t*i-mp[tmp];
			break;
		}
	}
	if(ans!=-1) printf("%lld\n",ans);
	else puts("no solution");
	return 0;
}

例題

[SDOI2013]隨機數生成器

用 吳potter 講的方法求出通項公式,接下來就是一個裸的 BSGS 了。

程式碼如下:

#include<bits/stdc++.h>

#define int long long
#define ll long long

using namespace std;

int T,len,block,ans;
ll p,a,b,x1,t;

map<ll,int>mp;

ll poww(ll a,ll b)
{
	ll ans=1;
	while(b)
	{
		if(b&1) ans=ans*a%p;
		a=a*a%p;
		b>>=1;
	}
	return ans;
}

signed main()
{
	scanf("%lld",&T);
	while(T--)
	{
		scanf("%lld%lld%lld%lld%lld",&p,&a,&b,&x1,&t);
		if(x1==t)
		{
			puts("1");
			continue;
		}
		if(!a)
		{
			if(b==t) puts("2");
			else puts("-1");
			continue;
		}
		if(a==1)
		{
			if(!b)
			{
				if(x1==t) puts("1");
				else puts("-1");
				continue;
			}
			t=(t-x1+p)%p;
			t=t*poww(b,p-2)%p;
			printf("%lld\n",t%p+1);
			continue;
		}
		t=(t+b*poww(a-1,p-2)%p)%p;
		ll tmp=(x1+b*poww(a-1,p-2)%p)%p;
		t=t*poww(tmp,p-2)%p;
		ans=-1;
		mp.clear();
		len=block=sqrt(p);
		while(len*block<p) block++;
		for(int i=1;i<=len;i++)
			mp[t*poww(a,i)%p]=i;
		for(int i=1;i<=block;i++)
		{
			ll tmp=poww(a,i*len);
			if(mp[tmp])
			{
				ans=i*len-mp[tmp];
				break;
			}
		}
		if(ans!=-1) printf("%lld\n",ans%p+1);
		else puts("-1");
	}
	return 0;
}
/*
1
398098159 1 0 391559262 79657487
*/

矩陣樹定理

原理

對於一個大小為 n 2 n^2 n2 的矩陣 A A A,定義其行列式 det ⁡ ( A ) \det(A) det(A) 為:( det ⁡ ( A ) \det(A) det(A) 也作 ∣ A ∣ |A| A

det ⁡ ( A ) = ∑ P ( − 1 ) μ ( P ) ∏ i = 1 n A ( i , p i ) \det(A)=\sum\limits_P (-1)^{\mu(P)}\prod\limits_{i=1}^{n}A(i,p_i) det(A)=P(1)μ(P)i=1nA(i,pi)

其中 P P P 1 ∼ n 1\sim n 1n 的一個排列, μ ( P ) \mu(P) μ(P) 是排列 P P P 的逆序對數。

也就是說,我們在矩陣的每一行選一個數(它們所在的列互不相同),然後乘起來。對於每一種選數方案,賦予它一個與逆序對有關的係數,再加起來。

矩陣樹定理有以下的性質:

  1. 交換矩陣的兩行,行列式變號。

    也就等同於交換一個 1 ∼ n 1\sim n 1n 排列的兩個數,整個序列改變的逆序對數為奇數。

    證明:

    設交換的這兩個數位置是在 i i i j j j 位置,分別是 x x x y y y

    我們把序列分成三段, A A A 段為 1 ∼ ( i − 1 ) 1\sim (i-1) 1(i1) B B B 段為 ( i + 1 ) ∼ ( j − 1 ) (i+1)\sim (j-1) (i+1)(j1) C C C 段為 ( j + 1 ) ∼ n (j+1)\sim n (j+1)n。(如圖)

    顯然,交換 x x x y y y 後, x x x A A A C C C 兩段的相對位置沒有改變, y y y A A A C C C 兩段的相對位置也沒有改變,所以由 ( A , x ) (A,x) (A,x) ( A , y ) (A,y) (A,y) ( x , C ) (x,C) (x,C) ( y , C ) (y,C) (y,C) 貢獻的逆序對數沒有改變。

    那麼我們只需要考慮 ( x , B ) (x,B) (x,B) ( B , y ) (B,y) (B,y) 兩種情況改變的逆序對數就好了。

    B B B 段中共有 S S S 個數,其中比 x x x 小的數有 a a a 個,比 y y y 大的數有 b b b 個,那麼這段中比 x x x 大的數有 S − a S-a Sa 個,比 y y y 小的有 S − b S-b Sb 個。

    可以知道交換前,這兩種情況貢獻的逆序對數共有 a + b a+b a+b 個。

    交換後,這兩種情況貢獻的逆序對數共有 ( S − a ) + ( S − b ) = 2 S − ( a + b ) (S-a)+(S-b)=2S-(a+b) (Sa)+(Sb)=2S(a+b) 個。

    前後貢獻的逆序對數之差為 [ 2 S − ( a + b ) ] − ( a + b ) = 2 ( S − a − b ) [2S-(a+b)]-(a+b)=2(S-a-b) [2S(a+b)](a+b)=2(Sab),為偶數。

    所以 ( x , B ) (x,B) (x,B) ( B , y ) (B,y) (B,y) 兩種情況改變的逆序對數為偶數對。

    注意到 x x x y y y 交換後, ( i , j ) (i,j) (i,j) 可能從一開始不是逆序對變成了逆序對,也可能從一開始是逆序對變成了不是逆序對,變動對數為奇數。

    所以總的變動對數為奇數對。

    證畢。

  2. 矩陣的某一行乘上 k k k,行列式也乘上 k k k

  3. ∣ a + a ′ b + b ′ c d ∣ = ∣ a b c d ∣ + ∣ a ′ b ′ c d ∣ \begin{vmatrix} a+a'&b+b'\\ c&d\end{vmatrix} =\begin{vmatrix} a&b\\ c&d\end{vmatrix} +\begin{vmatrix} a'&b'\\ c&d \end{vmatrix} a+acb+bd=acbd+acbd

  4. 若矩陣中某兩行相同,則它的行列式為 0 0 0

  5. 用矩陣的一行加上另一行的倍數,行列式不變。

其中上面的第 2 ∼ 4 2\sim 4 24 點的證明在這篇題解有詳細闡述,不會的可以直接去看。

發現第 5 5 5 點操作很像高斯消元中的操作,所以可以考慮用類似高斯消元的方法把行列式變成一個右上三角矩陣,然後這個矩陣的行列式就是對角線上的乘積。

以上是關於行列式和如何求行列式( O ( n 3 ) O(n^3) O(n3))。

接下來是矩陣樹定理:

  1. 無向圖。設 A A A 為鄰接矩陣, D D D 為度數矩陣( D ( i , i ) D(i,i) D(i,i) 為節點 i i i 的度數,其他的無值)。則基爾霍夫 (Kirchhoff)矩陣即為 : K = D − A K=D-A K=DA。把 K K K 去掉任意第 i i i 行和第 i i i 列後的矩陣 K ′ K' K 的行列式即為答案。

  2. 有根擴充套件。設根為 r t rt rt,則把 K K K 去掉第 r t rt rt 行和第 r t rt rt 列後的矩陣 K ′ K' K 的行列式即為答案。

  3. 有向擴充套件。若為外向樹,則度數矩陣統計的是入度;若為內向樹,則度數矩陣統計的是出度。

  4. 邊權擴充套件。假設有一條邊 ( u , v , w ) (u,v,w) (u,v,w),那麼可以看成是在 ( u , v ) (u,v) (u,v) 之間,一共有 w w w 條重邊。

    那麼我們把度數變成邊權就好了。

    此時矩陣樹定理求的就是 : 所有生成樹邊權乘積的總和。

以上擴充套件都可以參照這道題。

#include<bits/stdc++.h>

#define N 310
#define ll long long
#define mod 1000000007

using namespace std;

int n,m,t,tp;
ll ans=1,a[N][N];

ll poww(ll a,ll b)
{
	ll ans=1;
	while(b)
	{
		if(b&1) ans=ans*a%mod;
		a=a*a%mod;
		b>>=1;
	}
	return ans;
}

void work()//高斯消元
{
	for(int i=2;i<=n;i++)
	{
		if(!a[i][i])
		{
			for(int j=i+1;j<=n;j++)
			{
				if(a[j][i])
				{
					ans=-ans;
					swap(a[i],a[j]);
					break;
				}
			}
		}
		ll inv=poww(a[i][i],mod-2);
		for(int j=i+1;j<=n;j++)
		{
			ll tmp=a[j][i]*inv%mod;
			for(int k=i;k<=n;k++)
				a[j][k]=(a[j][k]-a[i][k]*tmp%mod+mod)%mod;
		}
	}
}

int main()
{
	scanf("%d%d%d",&n,&m,&t);
	for(int i=1;i<=m;i++)
	{
		int u,v,w;
		scanf("%d%d%d",&u,&v,&w);
		if(!t)
		{
			a[u][u]=(a[u][u]+w)%mod;
			a[v][v]=(a[v][v]+w)%mod;
			a[u][v]=(a[u][v]-w+mod)%mod;
			a[v][u]=(a[v][u]-w+mod)%mod;
		}
		else
		{
			a[v][v]=(a[v][v]+w)%mod;
			a[u][v]=(a[u][v]-w+mod)%mod;
		}
	}
	//以上是構造K矩陣
	work();
	for(int i=2;i<=n;i++)
		ans=ans*a[i][i]%mod;
	printf("%lld\n",ans);
	return 0;
}

例題

[FJOI2007]輪狀病毒

看題面顯然是矩陣樹定理。

可以使用高精度小數進行高斯消元,顯然不夠優雅。

發現題目給的圖結構類似,不妨手推一下行列式長啥樣。

發現得到的行列式如下:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-AYqqOGyg-1604621224497)(http://192.168.102.138/JudgeOnline/upload/attachment/image/20180615/20180615085710_76849.png)]

再推一下行列式的性質,發現: f i = 3 f i − 1 − f i − 2 + 2 f_i=3f_{i-1}-f_{i-2}+2 fi=3fi1fi2+2(其中 f i f_i fi n = i n=i n=i 的答案)。

然後用高精度遞推就好了。

至於矩陣快速冪就不用了……

程式碼如下:

#include<bits/stdc++.h>

#define N 110

using namespace std;

struct Big_Number
{
	int len,a[50];
	Big_Number(){memset(a,0,sizeof(a));}
	Big_Number(int x)
	{
		len=0;
		memset(a,0,sizeof(a));
		while(x)
		{
			a[++len]=x%10;
			x/=10;
		}
	}
}f[N];

Big_Number operator + (Big_Number a,Big_Number b)
{
	Big_Number c;
	c.len=max(a.len,b.len);
	for(int i=1;i<=c.len;i++)
	{
		c.a[i]+=a.a[i]+b.a[i];
		if(c.a[i]>=10)
		{
			c.a[i]-=10;
			c.a[i+1]++;
		}
	}
	while(c.a[c.len+1]) c.len++;
	return c;
}

Big_Number operator - (Big_Number a,Big_Number b)
{
	Big_Number c;
	c.len=max(a.len,b.len);
	for(int i=1;i<=c.len;i++)
	{
		c.a[i]+=a.a[i]-b.a[i];
		if(c.a[i]<0)
		{
			c.a[i]+=10;
			c.a[i+1]--;
		}
	}
	while(!c.a[c.len]) c.len--;
	return c;
}

Big_Number operator * (Big_Number a,Big_Number b)
{
	Big_Number c;
	c.len=a.len+b.len-1;
	for(int i=1;i<=a.len;i++)
	{
		for(int j=1;j<=b.len;j++)
		{
			c.a[i+j-1]+=a.a[i]*b.a[j];
			c.a[i+j]+=c.a[i+j-1]/10;
			c.a[i+j-1]%=10;
		}
	}
	while(c.a[c.len+1]) c.len++;
	return c;
}

void print(Big_Number a)
{
	for(int i=a.len;i;i--)
		putchar(a.a[i]+'0');
	puts("");
}

int n;

int main()
{
	scanf("%d",&n);
	f[1]=Big_Number(1);
	f[2]=Big_Number(5);
	for(int i=3;i<=n;i++)
		f[i]=(f[i-1]*Big_Number(3))-f[i-2]+Big_Number(2);
	print(f[n]);
	return 0;
}

[HEOI2015]小 Z 的房間

題解

[SHOI2016]黑暗前的幻想鄉

待填坑……

積性函式

原理

積性函式:對於一個函式 f ( x ) f(x) f(x),若有 f ( a b ) = f ( a ) f ( b ) f(ab)=f(a)f(b) f(ab)=f(a)f(b),其中 gcd ⁡ ( a , b ) = 1 \gcd(a,b)=1 gcd(a,b)=1,則稱 f ( x ) f(x) f(x) 為積性函式。

完全積性函式:對於一個函式 f ( x ) f(x) f(x),若有 f ( a b ) = f ( a ) f ( b ) f(ab)=f(a)f(b) f(ab)=f(a)f(b),則稱 f ( x ) f(x) f(x) 為完全積性函式。

常見的積性函式及其求法:

  1. 尤拉函式( φ \varphi φ

    定義: φ ( n ) \varphi(n) φ(n) 是小於或等於 n n n 的正整數中與 n n n 互質的數的個數。

    求法:

    首先對於 p ∈ p r i m e p\in prime pprime,肯定有 φ ( p ) = p − 1 \varphi(p)=p-1 φ(p)=p1

    然後對於 p ∈ p r i m e p\in prime pprime,有 φ ( p k ) = p k − p k − 1 \varphi(p^k)=p^{k}-p^{k-1} φ(pk)=pkpk1,因為除了 p p p 的倍數以外的其他數都與 p k p^k pk 互質。

    再加上它是個積性函式,就可以用線性篩求 φ \varphi φ 了。

  2. 莫比烏斯函式( μ \mu μ

    μ ( n ) = { 1 n = 1 ( − 1 ) k n  無大於  1  的平方數因數, n  分解質因數為  n = p 1 p 2 ⋯ p k 0 n  有大於  1  的平方數因數 \mu(n)= \begin{cases} 1&n=1\\ (-1)^k& \text{$n$ 無大於 $1$ 的平方數因數,$n$ 分解質因數為 $n=p_1p_2\cdots p_k$}\\ 0 & \text{$n$ 有大於 $1$ 的平方數因數} \end{cases} μ(n)=1(1)k0n=1n 無大於 1 的平方數因數,n 分解質因數為 n=p1p2pkn 有大於 1 的平方數因數

    根據這個定義,我們就可以線性篩了。

常見的完全積性函式:

  1. ϵ ( n ) = [ n = 1 ] \epsilon(n)=[n=1] ϵ(n)=[n=1]

  2. I ( n ) = 1 I(n)=1 I(n)=1

  3. i d ( n ) = n id(n)=n id(n)=n

例題

待填坑……

狄利克雷卷積

原理

f , g f,g f,g 是兩個積性函式,定義他們的狄利克雷卷積是: ( f ∗ g ) ( n ) = ∑ d ∣ n f ( d ) g ( n d ) (f*g)(n)=\sum\limits_{d|n}f(d)g(\dfrac{n}{d}) (fg)(n)=dnf(d)g(dn)

它滿足交換律、結合律。

然後對於任意的 f f f,有 f ∗ ϵ = f f*\epsilon=f fϵ=f

常見的狄利克雷卷積有:

  1. μ ∗ I = ϵ \mu*I=\epsilon μI=ϵ,即 ∑ d ∣ n μ ( d ) = [ n = 1 ] \sum\limits_{d|n}\mu(d)=[n=1] dnμ(d)=[n=1]

    證明:

    n = 1 n=1 n=1,則易得原式等於 1 1 1

    n ≠ 1 n\neq 1 n=1,將 n n n 質因數分解得 n = p 1 a 1 p 2 a 2 ⋯ p m a m n=p_1^{a_1}p_2^{a_2}\cdots p_m^{a_m} n=p1a1p2a2pmam

    設列舉到的 d = p 1 x 1 p 2 x 2 ⋯ p m x m d=p_1^{x_1}p_2^{x_2}\cdots p_m^{x_m} d=p1x1p2x2pmxm

    按列舉到的 d d d 分類討論:

    1. 對於所有的 x i x_i xi,存在有至少一個 x i > 1 x_i>1 xi>1

      此時 μ ( d ) = 0 \mu(d)=0 μ(d)=0

    2. 對於所有的 x i x_i xi,都有 x i ≤ 1 x_i\leq 1 xi1

      那麼這部分的 a n s ans ans 為:

      a n s = ∑ i = 0 m ( m i ) ( − 1 ) i ans=\sum_{i=0}^{m}\binom{m}{i}(-1)^i ans=i=0m(im)(1)i

      其中第一個 ∑ i = 0 m \sum\limits_{i=0}^m i=0m 列舉所有的 x j x_j xj 中( 1 ≤ j ≤ m 1\leq j \leq m 1jm),有 i i i x j x_j xj 大於 0 0 0,即有 i i i x j x_j xj 1 1 1

      然後 ( m i ) \dbinom{m}{i} (im) 意思是從所有的 x j x_j xj 中( 1 ≤ j ≤ m 1\leq j \leq m 1jm),選出 i i i x j x_j xj 1 1 1 的方案數。

      然後 ( − 1 ) i (-1)^i (1)i 就是 μ ( d ) \mu(d) μ(d)

      至於 a n s ans ans 為什麼等於 0 0 0

      1. m ≡ 1 ( m o d 2 ) m\equiv 1\pmod 2 m1(mod2)

        + ( m 0 ) +\dbinom{m}{0} +(0m) 會和 − ( m m ) -\dbinom{m}{m} (mm) 抵消, − ( m 1 ) -\dbinom{m}{1} (1m) 會和 + ( m m − 1 ) +\dbinom{m}{m-1} +(m1m) 抵消……以此類推,可得 a n s = 0 ans=0 ans=0

      2. m ≡ 0 ( m o d 2 ) m\equiv 0 \pmod 2 m0(mod2)

        將每個 ( m i ) \dbinom{m}{i} (im) 都畫在楊輝三角里:

        a n s = ( m 0 ) − ( m 1 ) + ( m 2 ) − ⋯ − ( m m − 1 ) + ( m m ) = ( m − 1 0 ) − [ ( m − 1 0 ) + ( m − 1 1 ) ] + [ ( m − 1 1 ) + ( m − 1 2 ) ] − ⋯ − [ ( m − 1 m − 2 ) + ( m − 1 m − 1 ) ] + ( m − 1 m − 1 ) = 0 \begin{aligned} ans=&\binom{m}{0}-\binom{m}{1}+\binom{m}{2}-\cdots-\binom{m}{m-1}+\binom{m}{m}\\ =&\binom{m-1}{0}-\left[\binom{m-1}{0}+\binom{m-1}{1}\right]+\left[\binom{m-1}{1}+\binom{m-1}{2}\right]-\cdots\\ &-\left[\binom{m-1}{m-2}+\binom{m-1}{m-1}\right]+\dbinom{m-1}{m-1}\\ =&0 \end{aligned} ans===(0m)(1m)+(2m)(m1m)+(mm)(0m1)[(0m1)+(1m1)]+[(1m1)+(2m1)][(m2m1)+(m1m1)]+(m1m1)0

      所以無論如何,都有 a n s = 0 ans=0 ans=0

    所以當 n ≠ 1 n\neq 1 n=1 時,原式為 0 0 0

    證畢。

  2. φ ∗ I = i d \varphi*I=id φI=id,即 ∑ d ∣ n φ ( d ) = n \sum\limits_{d|n}\varphi(d)=n dnφ(d)=n

    證明:

    首先把式子轉換一下: ∑ d ∣ n φ ( d ) = ∑ d ∣ n φ ( n d ) \sum\limits_{d|n}\varphi(d)=\sum\limits_{d|n}\varphi(\dfrac{n}{d}) dnφ(d)=dnφ(dn)

    考慮每一個 φ ( n d ) \varphi(\dfrac{n}{d}) φ(dn) 代表著什麼。

    可以把 φ ( n d ) \varphi(\dfrac{n}{d}) φ(dn) 看成小於等於 n n n 的所有正整數中,與 n n n gcd ⁡ \gcd gcd d d d 的個數。

    那麼 ∑ d ∣ n φ ( n d ) \sum\limits_{d|n}\varphi(\dfrac{n}{d}) dnφ(dn) 就可以看成所有小於等於 n n n 的正整數個數,得 ∑ d ∣ n φ ( n d ) = n \sum\limits_{d|n}\varphi(\dfrac{n}{d})=n dnφ(dn)=n

    證畢。

  3. μ ∗ i d = φ \mu*id=\varphi μid=φ,即 ∑ d ∣ n μ ( d ) × d = φ ( n ) \sum\limits_{d|n}\mu(d)\times d=\varphi(n) dnμ(d)×d=φ(n)

    證明:

    首先由(2)得 φ ∗ I = i d \varphi*I=id φI=id

    φ ∗ I ∗ μ = i d ∗ μ \varphi*I*\mu=id*\mu φIμ=idμ

    φ ∗ ϵ = i d ∗ μ \varphi*\epsilon=id*\mu φϵ=idμ

    φ = i d ∗ μ \varphi=id*\mu φ=idμ

    證畢。

莫比烏斯反演

原理

定理:若 g = f ∗ I g=f*I g=fI,即 g ( n ) = ∑ d ∣ n f ( d ) g(n)=\sum\limits_{d|n}f(d) g(n)=dnf(d),則 f = g ∗ μ f=g*\mu f=gμ,即 f ( n ) = ∑ d ∣ n g ( d ) μ ( n d ) f(n)=\sum\limits_{d|n}g(d)\mu(\dfrac{n}{d}) f(n)=dng(d)μ(dn)

證明: g = f ∗ I g=f*I g=fI g ∗ μ = f ∗ I ∗ μ = f ∗ ϵ = f g*\mu=f*I*\mu=f*\epsilon=f gμ=fIμ=fϵ=f,證畢。

例題

其實很多莫比烏斯反演的題都可以不用莫反做。

[國家集訓隊]Crash的數字表格/【BZOJ2693】jzptab

題解

杜教篩

原理

求一個積性函式 f f f 的字首和,記 S ( n ) = ∑ i = 1 n f ( i ) S(n)=\sum\limits_{i=1}^n f(i) S(n)=i=1nf(i)

再找一個積性函式 g g g,先考慮它們的狄利克雷卷積的字首和:

∑ i = 1 n ( f ∗ g ) ( i ) = ∑ i = 1 n ∑ d ∣ i f ( d ) g ( i d ) = ∑ d = 1 n g ( d ) ∑ i = 1 ⌊ n d ⌋ f ( i ) = ∑ d = 1 n g ( d ) S ( ⌊ n d ⌋ ) \begin{aligned} &\sum_{i=1}^{n}(f*g)(i)\\ =&\sum_{i=1}^n\sum_{d|i}f(d)g(\frac{i}{d})\\ =&\sum_{d=1}^ng(d)\sum_{i=1}^{\lfloor\frac{n}{d}\rfloor}f(i)\\ =&\sum_{d=1}^ng(d)S\left(\lfloor\frac{n}{d}\rfloor\right) \end{aligned} ===i=1n(fg)(i)i=1ndif(d)g(di)d=1ng(d)i=1dnf(i)d=1ng(d)S(dn)

考慮 g ( 1 ) S ( n ) g(1)S(n) g(1)S(n) 是什麼,發現:

g ( 1 ) S ( n ) = ∑ d = 1 n g ( d ) S ( ⌊ n d ⌋ ) − ∑ d = 2 n g ( d ) S ( ⌊ n d ⌋ ) = ∑ i = 1 n ( f ∗ g ) ( i ) − ∑ d = 2 n g ( d ) S ( ⌊ n d ⌋ ) \begin{aligned} g(1)S(n)&=\sum_{d=1}^ng(d)S\left(\lfloor\frac{n}{d}\rfloor\right)-\sum_{d=2}^ng(d)S\left(\lfloor\frac{n}{d}\rfloor\right)\\ &=\sum_{i=1}^{n}(f*g)(i)-\sum_{d=2}^ng(d)S\left(\lfloor\frac{n}{d}\rfloor\right) \end{aligned} g(1)S(n)=d=1ng(d)S(dn)d=2ng(d)S(dn)=i=1n(fg)(i)d=2ng(d)S(dn)

那麼我們就可以找到或構造一個 g g g,使得 ∑ i = 1 n ( f ∗ g ) ( i ) \sum\limits_{i=1}^{n}(f*g)(i) i=1n(fg)(i) g g g 的字首和可以快速算,然後數論分塊遞迴求解。

然後可以預處理出前若干個答案的字首和,並且加上記憶化,可以把時間複雜度優化成 O ( n 2 3 ) O(n^{\frac{2}{3}}) O(n32),其中要處理前 n 2 3 n^{\frac{2}{3}} n32 個答案的字首和。

程式碼如下:(【模板】杜教篩

#include<bits/stdc++.h>

#define N 1664510
#define int long long
#define ll long long

using namespace std;

int t,n;
int cnt,prime[N+10],mu[N+10];
bool notprime[N+10];
ll phi[N+10];

map<int,ll>summu;
map<int,int>sumphi;

void init()
{
	mu[1]=phi[1]=1;
	for(int i=2;i<=N;i++)
	{
		if(!notprime[i])
		{
			prime[++cnt]=i;
			mu[i]=-1;
			phi[i]=i-1;
		}
		for(int j=1;j<=cnt&&i*prime[j]<=N;j++)
		{
			notprime[i*prime[j]]=1;
			if(!(i%prime[j]))
			{
				phi[i*prime[j]]=phi[i]*prime[j];
				break;
			}
			mu[i*prime[j]]=-mu[i];
			phi[i*prime[j]]=phi[i]*phi[prime[j]];
		}
	}
	for(int i=2;i<=N;i++)
	{
		mu[i]+=mu[i-1];
		phi[i]+=phi[i-1];
	}
}

ll getsummu(int n)
{
	if(n<=N) return mu[n];
	if(summu[n]) return summu[n];
	ll ans=1;
	for(int l=2,r;l<=n;l=r+1)
	{
		r=n/(n/l);
		ans-=(r-l+1)*getsummu(n/l);
	}
	return summu[n]=ans;
}

ll getsumphi(int n)
{
	if(n<=N) return phi[n];
	if(sumphi[n]) return sumphi[n];
	ll ans=1ll*n*(n+1)/2;
	for(int l=2,r;l<=n;l=r+1)
	{
		r=n/(n/l);
		ans-=(r-l+1)*getsumphi(n/l);
	}
	return sumphi[n]=ans;
}

signed main()
{
	init();
	scanf("%lld",&t);
	while(t--)
	{
		scanf("%lld",&n);
		printf("%lld %lld\n",getsumphi(n),getsummu(n));
	}
	return 0;
}

例題

【BZOJ4916】神犇和蒟蒻

首先根據莫比烏斯函式的定義,第一問答案一定是 1 1 1

然後考慮第二問:

首先 φ ( i 2 ) = φ ( i ) × i \varphi(i^2)=\varphi(i)\times i φ(i2)=φ(i)×i

f ( n ) = φ ( n ) × n f(n)=\varphi(n)\times n f(n)=φ(n)×n

根據杜教篩的套路式:

g ( 1 ) S ( n ) = ∑ i = 1 n ( f ∗ g ) ( i ) − ∑ d = 2 n g ( d ) S ( ⌊ n d ⌋ ) g(1)S(n)=\sum_{i=1}^{n}(f*g)(i)-\sum_{d=2}^ng(d)S\left(\lfloor\frac{n}{d}\rfloor\right) g(1)S(n)=i=1n(fg)(i)d=2ng(d)S(dn)

我們需要找到一個 g g g 使得 f ∗ g f*g fg g g g 自己的字首和都可以快速算。

∑ i = 1 n ( f ∗ g ) ( i ) = ∑ i = 1 n ∑ d ∣ i φ ( d ) × d × g ( n d ) \sum\limits_{i=1}^{n}(f*g)(i)=\sum\limits_{i=1}^n \sum_{d|i}\varphi(d)\times d\times g(\dfrac{n}{d}) i=1n(fg)(i)=i=1ndiφ(d)×d×g(dn)

發現把 g g g 設為 i d id id 就好了,即 g ( n ) = n g(n)=n g(n)=n

那麼:

∑ i = 1 n ( f ∗ g ) ( i ) = ∑ i = 1 n ∑ d ∣ i φ ( d ) × d × g ( i d ) = ∑ i = 1 n ∑ d ∣ i φ ( d ) × d × i d = ∑ i = 1 n ∑ d ∣ i φ ( d ) × i = ∑ i = 1 n i ∑ d ∣ i φ ( d ) = ∑ i = 1 n i 2 = n ( n + 1 ) ( 2 n + 1 ) 6 \begin{aligned} \sum\limits_{i=1}^{n}(f*g)(i)&=\sum\limits_{i=1}^n \sum_{d|i}\varphi(d)\times d\times g(\dfrac{i}{d})\\ &=\sum\limits_{i=1}^n \sum_{d|i}\varphi(d)\times d\times \dfrac{i}{d}\\ &=\sum\limits_{i=1}^n \sum_{d|i}\varphi(d)\times i\\ &=\sum\limits_{i=1}^n i\sum_{d|i}\varphi(d)\\ &=\sum\limits_{i=1}^n i^2\\ &=\frac{n(n+1)(2n+1)}{6} \end{aligned} i=1n(fg)(i)=i=1ndiφ(d)×d×g(di)=i=1ndiφ(d)×d×di=i=1ndiφ(d)×i=i=1nidiφ(d)=i=1ni2=6n(n+1)(2n+1)

然後用杜教篩直接做就好了。

程式碼如下:

#include<bits/stdc++.h>
 
#define N 1000010
#define ll long long
#define mod 1000000007
 
using namespace std;
 
const int maxn=1000000;
 
int n;
int cnt,prime[N];
bool notprime[N];
ll phi[N];
 
map<int,ll>sum;
 
ll poww(ll a,ll b)
{
    ll ans=1;
    while(b)
    {
        if(b&1) ans=ans*a%mod;
        a=a*a%mod;
        b>>=1;
    }
    return ans;
}
 
void init()
{
    phi[1]=1;
    for(int i=2;i<=maxn;i++)
    {
        if(!notprime[i])
        {
            prime[++cnt]=i;
            phi[i]=i-1;
        }
        for(int j=1;j<=cnt&&i*prime[j]<=maxn;j++)
        {
            notprime[i*prime[j]]=1;
            if(!(i%prime[j]))
            {
                phi[i*prime[j]]=phi[i]*prime[j];
                break;
            }
            phi[i*prime[j]]=phi[i]*phi[prime[j]];
        }
    }
    for(int i=1;i<=maxn;i++)
        phi[i]=(phi[i-1]+i*phi[i]%mod)%mod;
}
 
ll dfs(int n)
{
    if(n<=maxn) return phi[n];
    if(sum[n]) return sum[n];
    ll ans=1ll*n*(n+1)%mod*(2*n+1)%mod*poww(6,mod-2)%mod;
    for(int l=2,r;l<=n;l=r+1)
    {
        r=n/(n/l);
        ans=(ans-1ll*(l+r)*(r-l+1)/2%mod*dfs(n/l)%mod+mod)%mod;
    }
    return sum[n]=ans;
}
 
int main()
{
    init();
    scanf("%d",&n);
    printf("1\n%lld",dfs(n));
    return 0;
}

[SDOI2015]約數個數和/【BZOJ4176】Lucas的數論(前者是弱化版)

∑ i = 1 n ∑ j = 1 m d ( i j ) \sum\limits_{i=1}^n\sum\limits_{j=1}^m d(ij) i=1nj=1md(ij),其中 d ( n ) d(n) d(n) n n n 的因數個數。

先證明一個式子: d ( i j ) = ∑ x ∣ i ∑ y ∣ j [ gcd ⁡ ( x , y ) = 1 ] d(ij)=\sum\limits_{x|i}\sum\limits_{y|j}[\gcd(x,y)=1] d(ij)=xiyj[gcd(x,y)=1]

證明:

i j ij ij 質因數分解,設 i j = p 1 c 1 p 2 c 2 ⋯ p k c k ij=p_1^{c_1}p_2^{c_2}\cdots p_k^{c_k} ij=p1c1p2c2pkck i = p 1 a 1 p 2 a 2 ⋯ p k a k i=p_1^{a_1}p_2^{a_2}\cdots p_k^{a_k} i=p1a1p2a2pkak j = p 1 b 1 p 2 b 2 ⋯ p k b k j=p_1^{b_1}p_2^{b_2}\cdots p_k^{b_k} j=p1b1p2b2pkbk。那麼易知對於任意的 l l l,均有 a l + b l = c l a_l+b_l=c_l al+bl=cl

我們將每一個質因數 p l p_l pl,我們分開考慮,因為質因數之間是互不影響的。

那麼等式左邊的貢獻是 p l c l p_l^{c_l} plcl 的因數個數,即 c l + 1 c_l+1 cl+1

等式右邊可以看成列舉 p l a l p_l^{a_l} plal 的因子 x x x p l b l p_l^{b_l} plbl 的因子 y y y 使得 x , y x,y x,y 互質。那麼只有 3 3 3 種情況: x = 1 x=1 x=1 y = p l 1 , p l 2 , ⋯   , p l b l y=p_l^1,p_l^2,\cdots,p_l^{b_l} y=pl1,pl2,,plbl,共 b l b_l bl 種情況; y = 1 y=1 y=1 x = p l 1 , p l 2 , ⋯   , p l a l x=p_l^1,p_l^2,\cdots,p_l^{a_l} x=pl1,pl2,,plal,共 a l a_l al 種情況; x = 1 x=1 x=1 y = 1 y=1 y=1,共 1 1 1 種情況。那麼右邊的總貢獻為 a l + b l + 1 = c l + 1 a_l+b_l+1=c_l+1 al+bl+1=cl+1

所以左右兩邊的貢獻是相等的。

證畢。(其實證明比較感性)

那麼就可以開始推式子了:(不妨設 n < m n<m n<m

∑ i = 1 n ∑ j = 1 m d ( i j ) = ∑ i = 1 n ∑ j = 1 m ∑ x ∣ i ∑ y ∣ j [ gcd ⁡ ( x , y ) = 1 ] = ∑ x = 1 n ∑ y = 1 m [ gcd ⁡ ( x , y ) = 1 ] ∑ x ∣ i ∑ y ∣ j 1 = ∑ x = 1 n ∑ y = 1 m ⌊ n x ⌋ ⌊ m y ⌋ [ gcd ⁡ ( x , y ) = 1 ] = ∑ x = 1 n ∑ y = 1 m ⌊ n x ⌋ ⌊ m y ⌋ ∑ d ∣ gcd ⁡ ( x , y ) μ ( d ) = ∑ d = 1 n μ ( d ) ∑ d ∣ x ⌊ n x ⌋ ∑ d ∣ y ⌊ m y ⌋ = ∑ d = 1 n μ ( d ) ∑ a = 1 ⌊ n d ⌋ ⌊ n a d ⌋ ∑ b = 1 ⌊ m d ⌋ ⌊ m b d ⌋ \begin{aligned} &\sum_{i=1}^n\sum_{j=1}^m d(ij)\\ =&\sum_{i=1}^n\sum_{j=1}^m\sum_{x|i}\sum_{y|j}[\gcd(x,y)=1]\\ =&\sum_{x=1}^n\sum_{y=1}^m[\gcd(x,y)=1]\sum_{x|i}\sum_{y|j}1\\ =&\sum_{x=1}^n\sum_{y=1}^m\lfloor\frac{n}{x}\rfloor\lfloor\frac{m}{y}\rfloor[\gcd(x,y)=1]\\ =&\sum_{x=1}^n\sum_{y=1}^m\lfloor\frac{n}{x}\rfloor\lfloor\frac{m}{y}\rfloor\sum_{d|\gcd(x,y)}\mu(d)\\ =&\sum_{d=1}^n\mu(d)\sum_{d|x}\lfloor\frac{n}{x}\rfloor\sum_{d|y}\lfloor\frac{m}{y}\rfloor\\ =&\sum_{d=1}^n\mu(d)\sum_{a=1}^{\lfloor\frac{n}{d}\rfloor}\lfloor\frac{n}{ad}\rfloor\sum_{b=1}^{\lfloor\frac{m}{d}\rfloor}\lfloor\frac{m}{bd}\rfloor \end{aligned} ======i=1nj=1md(ij)i=1nj=1mxiyj[gcd(x,y)=1]x=1ny=1m[gcd(x,y)=1]xiyj1x=1ny=1mxnym[gcd(x,y)=1]x=1ny=1mxnymdgcd(x,y)μ(d)d=1nμ(d)dxxndyymd=1nμ(d)a=1dnadnb=1dmbdm

式子就推到這了,弱化版和強化版的做法不一樣。

弱化版: T , n ≤ 5 × 1 0 4 T,n\leq 5\times 10^4 T,n5×104

O ( n ) O(n) O(n) 預處理 μ \mu μ O ( n n ) O(n\sqrt{n}) O(nn ) 預處理 g ( n ) = ∑ i = 1 n ⌊ n i ⌋ g(n)=\sum\limits_{i=1}^n \lfloor\dfrac{n}{i}\rfloor g(n)=i=1nin

然後 O ( T n ) O(T\sqrt{n}) O(Tn ) 詢問。

程式碼如下:

#include<bits/stdc++.h>

#define N 50010
#define ll long long

using namespace std;

int t,n,m;
int cnt,prime[N],mu[N],sum[N];
bool notprime[N];
ll g[N];

void init()
{
	mu[1]=1;
	for(int i=2;i<=50000;i++)
	{
		if(!notprime[i])
		{
			prime[++cnt]=i;
			mu[i]=-1;
		}
		for(int j=1;j<=cnt&&i*prime[j]<=50000;j++)
		{
			notprime[i*prime[j]]=1;
			if(!(i%prime[j])) break;
			mu[i*prime[j]]=-mu[i];
		}
	}
	for(int i=1;i<=50000;i++)
		sum[i]=sum[i-1]+mu[i];
	for(int i=1;i<=50000;i++)
	{
		for(int l=1,r;l<=i;l=r+1)
		{
			r=i/(i/l);
			g[i]+=1ll*(r-l+1)*(i/l);
		}
	}
}

int main()
{
	init();
	scanf("%d",&t);
	while(t--)
	{
		scanf("%d%d",&n,&m);
		if(n>m) swap(n,m);
		ll ans=0;
		for(int l=1,r;l<=n;l=r+1)
		{
			r=min(n/(n/l),m/(m/l));
			ans+=1ll*(sum[r]-sum[l-1])*g[n/l]*g[m/l];
		}
		printf("%lld\n",ans);
	}
	return 0;
}

強化版: T = 1 , n ≤ 1 0 9 T=1,n\leq 10^9 T=1,n109

用杜教篩來優化 μ ( n ) \mu(n) μ(n) 的字首和。

#include<bits/stdc++.h>
 
#define N 1000010
#define ll long long
#define mod 1000000007
 
using namespace std;
 
int n;
int cnt,mu[N],sum[N],prime[N];
bool notprime[N];
 
map<int,int>mp;
 
void init()
{
    mu[1]=1;
    for(int i=2;i<=1e6;i++)
    {
        if(!notprime[i])
        {
            prime[++cnt]=i;
            mu[i]=-1;
        }
        for(int j=1;j<=cnt&&i*prime[j]<=1e6;j++)
        {
            notprime[i*prime[j]]=1;
            if(!(i%prime[j])) break;
            mu[i*prime[j]]=-mu[i];
        }
    }
    for(int i=1;i<=1e6;i++)
        sum[i]=sum[i-1]+mu[i];
}
 
ll calc(int n)
{
    ll ans=0;
    for(int l=1,r;l<=n;l=r+1)
    {
        r=n/(n/l);
        ans=(ans+1ll*(r-l+1)*(n/l))%mod;
    }
    return ans;
}
 
int summu(int n)
{
    if(n<=1e6) return sum[n];
    if(mp[n]) return mp[n];
    int ans=1;
    for(int l=2,r;l<=n;l=r+1)
    {
        r=n/(n/l);
        ans-=(r-l+1)*summu(n/l);
    }
    return mp[n]=ans;
}
 
int main()
{
    init();
    scanf("%d",&n);
    ll ans=0;
    for(int l=1,r;l<=n;l=r+1)
    {
        r=n/(n/l);
        ll tmp=calc(n/l);
        ans=(ans+(0ll+summu(r)-summu(l-1)+mod)%mod*tmp%mod*tmp%mod)%mod;
    }
    printf("%lld\n",ans);
    return 0;
}

多項式

原根

定義:若 g φ ( p ) ≡ 1 ( m o d p ) g^{\varphi(p)}\equiv 1\pmod{p} gφ(p)1(modp),且 φ ( p ) \varphi(p) φ(p) g x ≡ 1 ( m o d p ) g^x\equiv1\pmod{p} gx1(modp) 的最小正整數解,則稱 g g g p p p 的原根。

若有 p ∈ p r i m e p\in prime pprime,則 φ ( p ) = p − 1 \varphi(p)=p-1 φ(p)=p1,那麼 g p − 1 ≡ 1 ( m o d p ) g^{p-1}\equiv 1\pmod{p} gp11(modp),那麼有 g 0   m o d   p , g 1   m o d   p , ⋯   , g p − 2   m o d   p g^0\bmod p,g^1\bmod p,\cdots,g^{p-2}\bmod p g0modp,g1modp,,gp2modp 1 ∼ ( p − 1 ) 1\sim (p-1) 1(p1) 一一對映。(不一定 g i g^i gi i i i 對應)

證明:

首先對於 0 ≤ i ≤ p − 2 0\leq i\leq p-2 0ip2,肯定有 g i   m o d   p ∈ [ 1 , p − 1 ] g^i\bmod p\in[1,p-1] gimodp[1,p1]

因為如果 g i ≡ 0 ( m o d p ) g^i\equiv 0\pmod p gi0(modp),那麼對於 j ≥ i j\geq i ji,肯定有 g j ≡ 0 ( m o d p ) g^j\equiv 0\pmod{p} gj0(modp),那麼 g φ ( p ) ≡ 1 ( m o d p ) g^{\varphi(p)}\equiv 1\pmod{p} gφ(p)1(modp) 肯定不成立。

然後若存在 g i ≡ g j ( m o d p ) g^i\equiv g^j\pmod p gigj(modp) 0 ≤ j < i ≤ p − 2 0\leq j<i\leq p-2 0j<ip2),那麼 g i − j ≡ 1 ( m o d p ) g^{i-j}\equiv 1\pmod p gij1(modp),又 i − j < φ ( p ) i-j<\varphi(p) ij<φ(p),則與原根的定義違背。

所以 g 0   m o d   p , g 1   m o d   p , ⋯   , g p − 2   m o d   p g^0\bmod p,g^1\bmod p,\cdots,g^{p-2}\bmod p g0modp,g1modp,,gp2modp 1 ∼ ( p − 1 ) 1\sim (p-1) 1(p1) 一一對映。

證畢。

多項式求逆

給定多項式 F ( x ) F(x) F(x),求多項式 G ( x ) G(x) G(x) 使得 F ( x ) G ( x ) ≡ 1 ( m o d x n ) F(x)G(x)\equiv1\pmod {x^n} F(x)G(x)1(modxn),其中 F ( x ) G ( x ) F(x)G(x) F(x)G(x) 的係數對一個數 p p p 取模。(對於一個多項式 F ( x ) F(x) F(x) F ( x )   m o d   x n F(x)\bmod x^n F(x)modxn 的意思是隻看 F ( x ) F(x) F(x) 中的第 0 0 0 項到第 n − 1 n-1 n1 項)

用遞迴求解。(以下的所有多項式都是在係數模了 p p p 意義下的)

首先假設我們已經求出了 F ( x ) H ( x ) ≡ 1 ( m o d x ⌈ n 2 ⌉ ) F(x)H(x)\equiv 1 \pmod{x^{\left\lceil\frac{n}{2}\right\rceil}} F(x)H(x)1(modx2n)(記為 1 1 1 式),現在要求 F ( x ) G ( x ) ≡ 1 ( m o d x n ) F(x)G(x)\equiv 1 \pmod{x^n} F(x)G(x)1(modxn)

首先如果有 F ( x ) G ( x ) ≡ 1 ( m o d x n ) F(x)G(x)\equiv 1 \pmod{x^n} F(x)G(x)1(modxn),那麼一定有 F ( x ) G ( x ) ≡ 1 ( m o d x ⌈ n 2 ⌉ ) F(x)G(x)\equiv 1 \pmod{x^{\left\lceil\frac{n}{2}\right\rceil}} F(x)G(x)1(modx2n)(記為 2 2 2 式),因為等號右邊的那個 1 1 1 只能在常數項出現,其他項的係數都是 0 0 0

1 1 1 式減 2 2 2 式得 F ( x ) ( H ( x ) − G ( x ) ) ≡ 0 ( m o d x ⌈ n 2 ⌉ ) F(x)\left(H(x)-G(x)\right)\equiv 0\pmod{x^{\left\lceil\frac{n}{2}\right\rceil}} F(x)(H(x)G(x))0(modx2n)

由於 F ( x ) ≢ 0 ( m o d x ⌈ n 2 ⌉ ) F(x)\not\equiv 0\pmod{x^{\left\lceil\frac{n}{2}\right\rceil}} F(x)0(modx2n),所以 H ( x ) − G ( x ) ≡ 0 ( m o d x ⌈ n 2 ⌉ ) H(x)-G(x)\equiv 0 \pmod{x^{\left\lceil\frac{n}{2}\right\rceil}} H(x)G(x)0(modx2n)

然後等式兩邊平方,因為 H ( x ) − G ( x ) ≡ 0 ( m o d x n 2 ) H(x)-G(x)\equiv 0 \pmod{x^{\frac{n}{2}}} H(x)G(x)0(modx2n),即 H ( x ) − G ( x ) H(x)-G(x) H(x)G(x) 的第 0 0 0 項到第 ⌈ n 2 ⌉ − 1 \left\lceil\dfrac{n}{2}\right\rceil-1 2n1 項係數都是 0 0 0,那麼 ( H ( x ) − G ( x ) ) 2 (H(x)-G(x))^2 (H(x)G(x))2 肯定滿足第 0 0 0 項到第 n − 1 n-1 n1 項係數都是 0 0 0,所以有:

H ( x ) 2 + G ( x ) 2 − 2 G ( x ) H ( x ) ≡ 0 ( m o d x n ) H(x)^2+G(x)^2-2G(x)H(x)\equiv 0\pmod{x^n} H(x)2+G(x)22G(x)H(x)0(modxn)

兩邊同時乘 F ( x ) F(x) F(x) 得:

F ( x ) H ( x ) 2 + F ( x ) G ( x ) 2 − 2 F ( x ) G ( x ) H ( x ) ≡ 0 ( m o d x n ) F ( x ) H ( x ) 2 + G ( x ) − 2 H ( x ) ≡ 0 ( m o d x n ) \begin{aligned} F(x)H(x)^2+F(x)G(x)^2-2F(x)G(x)H(x)&\equiv 0\pmod{x^n}\\ F(x)H(x)^2+G(x)-2H(x)&\equiv 0\pmod{x^n}\\ \end{aligned} F(x)H(x)2+F(x)G(x)22F(x)G(x)H(x)F(x)H(x)2+G(x)2H(x)0(modxn)0(modxn)

上面第二步的化簡是用 F ( x ) G ( x ) ≡ 1 ( m o d x n ) F(x)G(x)\equiv 1 \pmod{x^n} F(x)G(x)1(modxn) 化簡的,注意不能用 F ( x ) H ( x ) ≡ 1 ( m o d x ⌈ n 2 ⌉ ) F(x)H(x)\equiv 1 \pmod{x^{\left\lceil\frac{n}{2}\right\rceil}} F(x)H(x)1(modx2n),因為兩個式子   m o d   \bmod mod 的東西不一樣。

那麼就可以得到: G ( x ) = 2 H ( x ) − F ( x ) H ( x ) 2 G(x)=2H(x)-F(x)H(x)^2 G(x)=2H(x)F(x)H(x)2

那麼我們就能通過 ⌈ n 2 ⌉ \left\lceil\dfrac{n}{2}\right\rceil 2n 的答案求出 n n n 時的答案了,所以一直向下遞迴。

最後當遞迴到 1 1 1 的時候,要求 F ( x ) G ( x ) ≡ 1 ( m o d x 1 ) F(x)G(x)\equiv 1\pmod{x^1} F(x)G(x)1(modx1),也就是 F ( x ) F(x) F(x) 的常數項和 G ( x ) G(x) G(x) 的常數項乘起來模 p p p 1 1 1,那麼只用求 F ( x ) F(x) F(x) 常數項的逆元就好了。

總時間複雜度為:(設 n = 2 m n=2^m n=2m,以下的 log ⁡ \log log 均以 2 2 2 為底)

∑ i = 0 m − 1 2 i log ⁡ ( 2 i ) < ∑ i = 0 m − 1 2 i log ⁡ ( n ) = ( 2 m − 1 + 1 − 1 ) log ⁡ ( n ) = ( n − 1 ) log ⁡ ( n ) < n log ⁡ n \sum\limits_{i=0}^{m-1}2^i\log(2^i)<\sum\limits_{i=0}^{m-1}2^i\log(n)=\left(2^{m-1+1}-1\right)\log(n)=(n-1)\log(n)<n\log n i=0m12ilog(2i)<i=0m12ilog(n)=(2m1+11)log(n)=(n1)log(n)<nlogn

程式碼如下:

#include<bits/stdc++.h>

#define N 200010
#define ll long long

using namespace std;

const int G=3,mod=998244353;

int n,rev[N<<1];
ll a[N<<1],b[N<<1],c[N<<1];

ll poww(ll a,ll b)
{
	ll ans=1;
	while(b)
	{
		if(b&1) ans=ans*a%mod;
		a=a*a%mod;
		b>>=1;
	}
	return ans;
}

void NTT(ll *a,int limit,int opt)
{
	for(int i=0;i<limit;i++)
		if(i<rev[i])
			swap(a[i],a[rev[i]]);
	for(int mid=1;mid<limit;mid<<=1)
	{
		int len=mid<<1;
		ll gn=poww(G,(mod-1)/len);
		for(int i=0;i<limit;i+=len)
		{
			ll g=1;
			for(int j=0;j<mid;j++,g=g*gn%mod)
			{
				ll x=a[i+j],y=a[i+mid+j];
				a[i+j]=(x+g*y)%mod,a[i+mid+j]=((x-g*y)%mod+mod)%mod;
			}
		}
	}
	if(opt==-1)
	{
		reverse(a+1,a+limit);
		ll tmp=poww(limit,mod-2);
		for(int i=0;i<limit;i++)
			a[i]=a[i]*tmp%mod;
	}
}

void solve(int n)
{
	if(n==1)
	{
		b[0]=poww(a[0],mod-2);
		return;
	}
	solve(ceil(1.0*n/2.0));
	int bit=0,limit=1;
	while(limit<(n<<1)) 
		bit++,limit<<=1;
	for(int i=0;i<limit;i++)
		rev[i]=(rev[i>>1]>>1)|((i&1)<<(bit-1));
	for(int i=0;i<n;i++)
		c[i]=a[i];
	for(int i=n;i<limit;i++)
		c[i]=0;
	NTT(b,limit,1),NTT(c,limit,1);
	for(int i=0;i<limit;i++)
		b[i]=((2*b[i]-c[i]*b[i]%mod*b[i]%mod)%mod+mod)%mod;
	NTT(b,limit,-1);
	for(int i=n;i<limit;i++) 
		b[i]=0;
}

int main()
{
	scanf("%d",&n);
	for(int i=0;i<n;i++)
		scanf("%lld",&a[i]);
	solve(n);
	for(int i=0;i<n;i++)
		printf("%lld ",b[i]);
	return 0;
}

多項式開根

跟多項式求逆差不多,用遞迴求解。

假設我們現在要求 G ( x ) G(x) G(x) 使得 G ( x ) 2 ≡ F ( x ) ( m o d x n ) G(x)^2\equiv F(x)\pmod{x^n} G(x)2F(x)(modxn),已經求出了 H ( x ) 2 ≡ F ( x ) ( m o d x ⌈ n 2 ⌉ ) H(x)^2\equiv F(x)\pmod{x^{\left\lceil\frac{n}{2}\right\rceil}} H(x)2F(x)(modx2n)。(記為 1 1 1 式)

顯然若 G ( x ) 2 ≡ F ( x ) ( m o d x n ) G(x)^2\equiv F(x)\pmod{x^n} G(x)2F(x)(modxn),則 G ( x ) ≡ F ( x ) ≡ H ( x ) ( m o d x ⌈ n 2 ⌉ ) G(x)\equiv \sqrt{F(x)}\equiv H(x)\pmod{x^{\left\lceil\frac{n}{2}\right\rceil}} G(x)F(x) H(x)(modx2n)

G ( x ) ≡ H ( x ) ( m o d x ⌈ n 2 ⌉ ) G ( x ) − H ( x ) ≡ 0 ( m o d x ⌈ n 2 ⌉ ) ( G ( x ) − H ( x ) ) 2 ≡ 0 ( m o d x n ) G ( x ) 2 + H ( x ) 2 − 2 G ( x ) H ( x ) ≡ 0 ( m o d x n ) F ( x ) + H ( x ) 2 − 2 G ( x ) H ( x ) ≡ 0 ( m o d x n ) G ( x ) ≡ F ( x ) + H ( x ) 2 2 H ( x ) ( m o d x n ) \begin{aligned} G(x)&\equiv H(x)\pmod{x^{\left\lceil\frac{n}{2}\right\rceil}}\\ G(x)-H(x)&\equiv 0\pmod{x^{\left\lceil\frac{n}{2}\right\rceil}}\\ (G(x)-H(x))^2&\equiv 0\pmod{x^n}\\ G(x)^2+H(x)^2-2G(x)H(x)&\equiv 0\pmod{x^n}\\ F(x)+H(x)^2-2G(x)H(x)&\equiv 0\pmod{x^n}\\ G(x)&\equiv\frac{F(x)+H(x)^2}{2H(x)}\pmod{x^n} \end{aligned} G(x)G(x)H(x)(G(x)H(x))2G(x)2+H(x)22G(x)H(x)F(x)+H(x)22G(x)H(x)G(x)H(x)(modx2n)0(modx2n)0(modxn)0(modxn)0(modxn)2H(x)F(x)+H(x)2(modxn)

然後就能遞迴求逆做了。

時間複雜度 O ( n log ⁡ n ) O(n\log n) O(nlogn)

程式碼如下:

#include<bits/stdc++.h>

#define N 400010
#define ll long long
#define mod 998244353

using namespace std;

int n,rev[N];
ll inv2,f[N],g[N],ff[N],gg[N],fff[N];

ll poww(ll a,ll b)
{
	ll ans=1;
	while(b)
	{
		if(b&1) ans=ans*a%mod;
		a=a*a%mod;
		b>>=1;
	}
	return ans;
}

void NTT(int limit,int opt,ll *a)
{
	for(int i=0;i<limit;i++)
		if(i<rev[i])
			swap(a[i],a[rev[i]]);
	for(int mid=1;mid<limit;mid<<=1)
	{
		int len=mid<<1;
		ll gn=poww(3,(mod-1)/len);
		if(opt==-1) gn=poww(gn,mod-2);
		for(int i=0;i<limit;i+=len)
		{
			ll omega=1;
			for(int j=0;j<mid;j++,omega=omega*gn%mod)
			{
				ll x=a[i+j],y=a[i+mid+j];
				a[i+j]=(x+omega*y)%mod,a[i+mid+j]=((x-omega*y)%mod+mod)%mod;
			}
		}
	}
	if(opt==-1)
	{
		ll tmp=poww(limit,mod-2);
		for(int i=0;i<limit;i++)
			a[i]=a[i]*tmp%mod;
	}
}

void Inv(int n,ll *f,ll *g)
{
	g[0]=poww(f[0],mod-2);
	int bit=0,limit,now;
	for(now=1;now<(n<<1);now<<=1)
	{
		bit++,limit=now<<1;
		for(int i=0;i<now;i++)
			ff[i]=f[i];
		for(int i=0;i<limit;i++)
			rev[i]=(rev[i>>1]>>1)|((i&1)?now:0);
		NTT(limit,1,ff),NTT(limit,1,g);
		for(int i=0;i<limit;i++)
			g[i]=((2*g[i]-ff[i]*g[i]%mod*g[i]%mod)%mod+mod)%mod;
		NTT(limit,-1,g);
		for(int i=now;i<limit;i++)
			ff[i]=g[i]=0;
	}
	for(int i=0;i<now;i++) ff[i]=0;
	for(int i=n;i<now;i++) g[i]=0;
}

void Sqrt(int n,ll *f,ll *g)
{
	g[0]=sqrt(f[0]);
	int bit=0,limit,now;
	for(now=1;now<(n<<1);now<<=1)
	{
		bit++,limit=now<<1;
		Inv(now,g,gg);
		for(int i=0;i<now;i++)
			fff[i]=f[i];
		for(int i=0;i<limit;i++)
			rev[i]=(rev[i>>1]>>1)|((i&1)?now:0);
		NTT(limit,1,fff),NTT(limit,1,g),NTT(limit,1,gg);
		for(int i=0;i<limit;i++)
			g[i]=inv2*gg[i]%mod*((fff[i]+g[i]*g[i])%mod)%mod;
		NTT(limit,-1,g);
		for(int i=0;i<limit;i++)
			gg[i]=0;
		for(int i=now;i<limit;i++)
			fff[i]=g[i]=0;
	}
	for(int i=0;i<now;i++) fff[i]=0;
	for(int i=n;i<now;i++) g[i]=0;
}

int main()
{
	inv2=poww(2,mod-2);
	scanf("%d",&n);
	for(int i=0;i<n;i++)
		scanf("%lld",&f[i]);
	Sqrt(n,f,g);
	for(int i=0;i<n;i++)
		printf("%lld ",g[i]);
	return 0;
}

多項式求導

f ( x ) = ∑ i ≥ 0 a i x i f(x)=\sum\limits_{i\geq0}a_ix^i f(x)=i0aixi,則:

f ′ ( x ) = lim ⁡ d → 0 ∑ i ≥ 0 a i ( x + d ) i − a i x i d = lim ⁡ d → 0 ∑ i ≥ 0 a i ( x + d ) i − x i d \begin{aligned} f'(x)=&\lim_{d\rightarrow 0}\sum_{i\geq0}\frac{a_i(x+d)^i-a_ix^i}{d}\\ =&\lim_{d\rightarrow 0}\sum_{i\geq0}a_i\frac{(x+d)^i-x^i}{d} \end{aligned} f(x)==d0limi0dai(x+d)iaixid0limi0aid(x+d)ixi

我們現在只看 ( x + d ) i − x i d \dfrac{(x+d)^i-x^i}{d} d(x+d)ixi 怎麼求:

( x + d ) i − x i d = ∑ j = 0 i ( i j ) x j d i − j − x i d = ∑ j = 0 i − 1 ( i j ) x j d i − j + x i d 0 − x i d = ∑ j = 0 i − 1 ( i j ) x j d i − j d = ∑ j = 0 i − 1 ( i j ) x j d i − j − 1 \begin{aligned} &\frac{(x+d)^i-x^i}{d}\\ =&\frac{\sum\limits_{j=0}^i\dbinom{i}{j}x^jd^{i-j}-x^i}{d}\\ =&\frac{\sum\limits_{j=0}^{i-1}\dbinom{i}{j}x^jd^{i-j}+x^id^0-x^i}{d}\\ =&\frac{\sum\limits_{j=0}^{i-1}\dbinom{i}{j}x^jd^{i-j}}{d}\\ =&\sum\limits_{j=0}^{i-1}\dbinom{i}{j}x^jd^{i-j-1} \end{aligned} ====d(x+d)ixidj=0i(ji)xjdijxidj=0i1(ji)xjdij+xid0xidj=0i1(ji)xjdijj=0i1(ji)xjdij1

j < i − 1 j<i-1 j<i1 時, i − j − 1 > 0 i-j-1>0 ij1>0,又由於 d d d 趨近於 0 0 0,則 d i − j − 1 = 0 d^{i-j-1}=0 dij1=0

所以:

∑ j = 0 i − 1 ( i j ) x j d i − j − 1 = ( i i − 1 ) x i − 1 d 0 = i x i − 1 \sum\limits_{j=0}^{i-1}\dbinom{i}{j}x^jd^{i-j-1}=\dbinom{i}{i-1}x^{i-1}d^0=ix^{i-1} j=0i1(ji)xjdij1=(i1i)xi1d0=ixi1

那麼:

f ′ ( x ) = lim ⁡ d → 0 ∑ i ≥ 0 a i ( x + d ) i − x i d = ∑ i ≥ 1 a i i x i − 1 \begin{aligned} f'(x)=&\lim_{d\rightarrow 0}\sum_{i\geq0}a_i\frac{(x+d)^i-x^i}{d}\\ =&\sum_{i\geq 1}a_iix^{i-1} \end{aligned} f(x)==d0limi0aid(x+d)ixii1aiixi1

同樣的,若知道 f ′ ( x ) = ∑ i ≥ 0 a i x i f'(x)=\sum\limits_{i\geq 0}a_ix^i f(x)=i0aixi,那麼 f ( x ) = ∑ i ≥ 1 a i − 1 i x i f(x)=\sum\limits_{i\geq 1}\dfrac{a_{i-1}}{i}x^i f(x)=i1iai1xi

一些特殊形式的求法

一、求 ∑ k = 1 n x k ∑ i − j = k a i b j \sum\limits_{k=1}^{n}x^k\sum\limits_{i-j=k}a_ib_j k=1nxkij=kaibj

平常的 FFT 是求 ∑ k = 1 n x k ∑ i + j = k a i b j \sum\limits_{k=1}^{n}x^k\sum\limits_{i+j=k}a_ib_j k=1nxki+j=kaibj,那麼我現在讓你求 ∑ k = 1 n x k ∑ i − j = k a i b j \sum\limits_{k=1}^{n}x^k\sum\limits_{i-j=k}a_ib_j k=1nxkij=kaibj

S ( k ) = ∑ i − j = k a i b j S(k)=\sum\limits_{i-j=k}a_ib_j S(k)=ij=kaibj,那麼題目是要求 ∑ k = 1 n x k S ( k ) \sum\limits_{k=1}^nx^kS(k) k=1nxkS(k)

a i ^ = a n − i \widehat{a_i}=a_{n-i} ai =ani,那麼:

S ( k ) = ∑ i − j = k a i b j = ∑ ( n − i ) − j = k a i ^ b j = ∑ i + j = n − k a i ^ b j \begin{aligned} S(k)=&\sum\limits_{i-j=k}a_ib_j\\ =&\sum_{(n-i)-j=k}\widehat{a_i}b_j\\ =&\sum_{i+j=n-k}\widehat{a_i}b_j \end{aligned} S(k)===ij=kaibj(ni)j=kai bji+j=nkai bj

那麼:

S ( n − k ) = ∑ i + j = n − ( n − k ) a i ^ b j = ∑ i + j = k a i ^ b j S(n-k)=\sum_{i+j=n-(n-k)}\widehat{a_i}b_j=\sum_{i+j=k}\widehat{a_i}b_j S(nk)=i+j=n(nk)ai bj=i+j=kai bj

那麼就可以設多項式 A = { a i ^ } A=\{\widehat{a_i}\} A={ai } B = { b i } B=\{b_i\} B={bi} C = A × B C=A\times B C=A×B,則 S ( n − k ) = [ x k ] C S(n-k)=[x^k]C S(nk)=[xk]C

所以將 C C C 翻轉一下就是我們要求的了。

例題

[SDOI2015]序列統計

首先 x > 1 x>1 x>1 所以數列裡不可能取 0 0 0

然後又考慮到原根的性質: g 0   m o d   m , g 1   m o d   m , ⋯   , g m − 2   m o d   m g^0\bmod m,g^1\bmod m,\cdots,g^{m-2}\bmod m g0modm,g1modm,,gm2modm 1 ∼ ( m − 1 ) 1\sim (m-1) 1(m1) 一一對映,所以考慮用 g 0   m o d   m , g 1   m o d   m , ⋯   , g m − 2   m o d   m g^0\bmod m,g^1\bmod m,\cdots,g^{m-2}\bmod m g0modm,g1modm,,gm2modm 代替 1 ∼ ( m − 1 ) 1\sim (m-1) 1(m1)

假設 x x x 對應的是 g y g^y gy,題目給定的 S = { a 1 , a 2 , ⋯   , a s } S=\{a_1,a_2,\cdots,a_{s}\} S={a1,a2,,as},它們對應的集合是 { g b 1 , g b 2 , ⋯   , g b s } \{g^{b_1},g^{b_2},\cdots,g^{b_s}\} {gb1,gb2,,gbs},設集合 T = { b 1 , b 2 , ⋯   , b s } T=\{b_1,b_2,\cdots,b_s\} T={b1,b2,,bs}

原來的問題是:從 S S S 中選 n n n 個可以重複的數排成一個序列,使得這 n n n 個數的乘積 x x x 的方案數。

那麼現在的問題是:從 T T T 中選 n n n 個可以重複的數排成一個序列,使得這 n n n 個數的加和 y y y 的方案數。

那麼這就可以用 NTT 來搞了。

具體過程就是設 c i c_i ci 表示 i i i 這個數有沒有在 T T T 中出現,設 c i c_i ci 的生成函式為 F ( x ) F(x) F(x),那麼 F ( x ) n F(x)^n F(x)n 就能表示這個加和的過程了,用快速冪實現。

#include<bits/stdc++.h>
 
#define M 8010
#define ll long long
 
using namespace std;
 
const int mod=1004535809;
 
int n,m,x,size;
int G,mp[M];
int limit,bit,rev[M<<2];
ll f[M<<2],ff[M<<2],g[M<<2];
bool vis[M];
 
ll poww(ll a,ll b,ll mod)
{
    ll ans=1;
    while(b)
    {
        if(b&1) ans=ans*a%mod;
        a=a*a%mod;
        b>>=1;
    }
    return ans;
}
 
void init()
{
    for(int i=2;;i++)
    {
    	memset(vis,0,sizeof(vis));
    	int tmp=1;
    	bool flag=true;
        for(int j=0;j<=m-2;j++)
        {
        	if(!tmp||vis[tmp])
        	{
        		flag=false;
        		break;
			}
			vis[tmp]=1;
        	tmp=tmp*i%m;
		}
		if(flag)
		{
			G=i;
			tmp=1;
			for(int j=0;j<=m-2;j++)
			{
				mp[tmp]=j;
				tmp=tmp*G%m;
			}
			break;
		}
    }
}
 
void NTT(ll *a,int opt)
{
    for(int i=0;i<limit;i++)
        if(i<rev[i])
            swap(a[i],a[rev[i]]);
    for(int mid=1;mid<limit;mid<<=1)
    {
        int len=mid<<1;
        ll gn=poww(3,(mod-1)/len,mod);//這裡不用G而是用3是因為這裡的原根是針對模數的,而不是m(一直50分沒調出來就是在這qaq)
        if(opt==-1) gn=poww(gn,mod-2,mod);
        for(int i=0;i<limit;i+=len)
        {
            ll omega=1;
            for(int j=0;j<mid;j++,omega=omega*gn%mod)
            {
                ll x=a[i+j],y=omega*a[i+mid+j]%mod;
                a[i+j]=(x+y)%mod,a[i+mid+j]=(x-y+mod)%mod;
            }
        }
    }
    if(opt==-1)
    {
        ll tmp=poww(limit,mod-2,mod);
        for(int i=0;i<limit;i++)
            a[i]=a[i]*tmp%mod;
    }
}
 
void mul(ll *f,ll *g)
{
    NTT(f,1),NTT(g,1);
    for(int i=0;i<limit;i++)
        f[i]=f[i]*g[i]%mod;
    NTT(f,-1),NTT(g,-1);
    for(int i=0;i<m;i++)
        f[i]=(f[i]+f[i+m])%mod;
    for(int i=m;i<limit;i++)
        f[i]=g[i]=0;
}
 
void polypow(ll *f,ll *g,int b)
{
    limit=1,bit=0;
    while(limit<(m<<1))
        limit<<=1,bit++;
    for(int i=0;i<limit;i++) 
        rev[i]=(rev[i>>1]>>1)|((i&1)<<(bit-1));
    while(b)
    {
        if(b&1) mul(g,f);
        for(int i=0;i<limit;i++)
			ff[i]=f[i];
        mul(f,ff);
        b>>=1;
    }
}

int main()
{
    scanf("%d%d%d%d",&n,&m,&x,&size);
    init();
    for(int i=1;i<=size;i++)
    {
        int x;
        scanf("%d",&x);
        if(!x) continue;
        f[mp[x]]++;
    }
    m--;
    g[0]=1;
    polypow(f,g,n);
    printf("%lld\n",g[mp[x]]);
    return 0;
}

拉格朗日插值

原理

給出 n n n 個點 ( x i , y i ) (x_i,y_i) (xi,yi),可知這 n n n 個點唯一確定一個多項式 f ( x ) f(x) f(x)

現在詢問 k k k,求 f ( k ) f(k) f(k)

用高斯消元可以做到預處理 O ( n 3 ) O(n^3) O(n3),每次詢問 O ( n ) O(n) O(n)

但是現在只有一次詢問,且 n n n 很大。

所以我們考慮用詢問更快的方法求解。

拉格朗日插值是通過構造的方法,在 O ( n 2 ) O(n^2) O(n2) 的時間內進行詢問。

設拉格朗日基本多項式為:

ℓ i ( x ) = ∏ j = 1 , j ≠ i n x − x j x i − x j \ell_i(x)=\prod_{j=1,j\neq i}^{n}\frac{x-x_j}{x_i-x_j} i(x)=j=1,j=inxixjxxj

注意到當 x = x i x=x_i x=xi 時, ℓ i ( x ) = 1 \ell_i(x)=1 i(x)=1;當 x = x j x=x_j x=xj j ≠ i j\neq i j=i)時, ℓ i ( x ) = 0 \ell_i(x)=0 i(x)=0

那麼就能把這個多項式 f ( x ) f(x) f(x) 構造出來了:

f ( x ) = ∑ i = 1 n y i ℓ i ( x ) f(x)=\sum_{i=1}^n y_i\ell_i(x) f(x)=i=1nyii(x)

顯然,這個多項式滿足對於任意的 i i i f ( x i ) = y i f(x_i)=y_i f(xi)=yi

那麼這個多項式就是我們要找的多項式。

#include<bits/stdc++.h>

#define N 2010
#define ll long long
#define mod 998244353

using namespace std;

int n;
ll k,x[N],y[N];

ll poww(ll a,ll b)
{
	ll ans=1;
	while(b)
	{
		if(b&1) ans=ans*a%mod;
		a=a*a%mod;
		b>>=1;
	}
	return ans;
}

int main()
{
	scanf("%d%lld",&n,&k);
	for(int i=1;i<=n;i++)
		scanf("%lld%lld",&x[i],&y[i]);
	ll ans=0;
	for(int i=1;i<=n;i++)
	{
		ll sum=1;
		for(int j=1;j<=n;j++)
		{
			if(i==j) continue;
			sum=sum*(k-x[j])%mod*poww(x[i]-x[j],mod-2)%mod;
		}
		ans=(ans+y[i]*sum)%mod;
	}
	printf("%lld\n",(ans+mod)%mod);
	return 0;
}

相關文章