zoukankan      html  css  js  c++  java
  • 数学小结(持续更新)

    数学小结

    前言:这篇文章专门整理了各种有关数学的东西(包括原理和例题),主要是怕自己忘了。

    整理的不齐全,而且写的都是比较基础的东西。

    扩展欧几里得(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'=amod b=a-lfloordfrac{a}{b} floor imes 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,amod 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 ) egin{aligned} a'x'+b'y'&=gcd(a',b')\ bx'+left(a-lfloordfrac{a}{b} floor imes b ight)y'&=gcd(a,b)\ ay'+bleft(x'-lfloordfrac{a}{b} floor imes y' ight)&=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 ′ egin{cases} x=y'\ y=x'-lfloordfrac{a}{b} floor imes 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 imes xequiv 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}mod p a1modp 的值。(保证 gcd ⁡ ( a , p ) = 1 gcd(a,p)=1 gcd(a,p)=1

    1. p ∈ p r i m e pin prime pprime,根据费马小定理, a p − 1 ≡ 1 ( m o d p ) a^{p-1}equiv 1pmod p ap11(modp),即 a × a p − 2 ≡ 1 ( m o d p ) a imes a^{p-2}equiv 1 pmod p a×ap21(modp),那么答案即为 a p − 2   m o d   p a^{p-2}mod p ap2modp

    2. p ∉ p r i m e p otin prime pprime,那么 a × x ≡ 1 ( m o d p ) a imes xequiv 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 ) egin{cases} xequiv a_1 pmod{p_{1}}\ xequiv a_2 pmod{p_{2}}\ cdots\ xequiv 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=prodlimits_{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 imes t_iequiv 1 pmod{p_i} mi×ti1(modpi)

    那么这个方程的一个特殊解就是: x 0 = ∑ i = 1 k a i m i t i x_0=sumlimits_{i=1}^{k}a_im_it_i x0=i=1kaimiti

    证明:

    对于任意的 i i i,我们考虑 x 0 ≡ a i ( m o d p i ) x_0equiv 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=sumlimits_{j=1,j eq i}^{k}a_jm_jt_j+a_im_it_i x0=j=1,j=ikajmjtj+aimiti

    显然,对于任意的 j ≠ i j eq i j=i a j m j t j   m o d   p i a_jm_jt_jmod 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_jmod p_i mjmodpi 0 0 0

    那么就可以得到 ∑ j = 1 , j ≠ i k a j m j t j ≡ 0 ( m o d p i ) sumlimits_{j=1,j eq i}^{k}a_jm_jt_jequiv 0 pmod{p_i} j=1,j=ikajmjtj0(modpi)

    然后因为 m i t i ≡ 1 ( m o d p i ) m_it_iequiv 1 pmod{p_i} miti1(modpi),得 a i m i t i ≡ a i ( m o d p i ) a_im_it_iequiv 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=sumlimits_{j=1,j eq i}^{k}a_jm_jt_j+a_im_it_iequiv 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_0mod 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 ) egin{cases} xequiv a_1 pmod{p_{1}}\ xequiv a_2 pmod{p_{2}}\ cdots\ xequiv 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=prodlimits_{j=1}^{i}p_j Mi=j=1ipj

    那么前 i i i 个方程组的通解为 x i = x 0 + M i × t x_i=x_0+M_i imes t xi=x0+Mi×t t ∈ Z tin Z tZ)。

    我们现在就是要找到一个 t t t,使得 x 0 + M i × t ≡ a i ( m o d p i ) x_0+M_i imes tequiv a_ipmod{p_{i}} x0+Mi×tai(modpi),即 M i × t ≡ a i − x 0 ( m o d p i ) M_i imes tequiv a_i-x_0pmod{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}mod p (mn)modp 的值,其中 p ∈ p r i m e pin 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)^pequiv x^p+1pmod 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)^pequiv sumlimits_{i=0}^{p}dbinom{p}{i}x^ipmod 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 imes (p-1) imes cdots imes(p-i+1)}{1 imes 2 imes cdots imes i} (ip)=1×2××ip×(p1)××(pi+1) p p p 的倍数,因为 p p p 为质数, 1 ∼ i 1sim 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 ) egin{aligned} &(x+1)^n\ equiv&(x+1)^{ap+b}\ equiv&(x+1)^{ap} imes(x+1)^b\ equiv&((x+1)^p)^a imes (x+1)^b\ equiv&(x^p+1)^a imes(x+1)^b\ equiv&left(sumlimits_{i=1}^{a}inom{a}{i}x^{ip} ight) imesleft(sum_{j=1}^{b}inom{b}{j}x^j ight)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 1leq jleq bleq 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} imesdbinom{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} imesdbinom{b}{r}=dbinom{lfloorfrac{n}{p} floor}{lfloorfrac{m}{p} floor} imesdbinom{nmod p}{mmod 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 0leq 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_{lfloorfrac{i}{2} floor} 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}} imes f_{lc} imes 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
    ",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
    ",F(n,k));
    	}
    	return 0;
    }
    

    [BJWC2018]上学路线

    题解

    Baby Step Giant Step(BSGS,北上广深

    原理

    a x ≡ b ( m o d p ) a^xequiv b pmod p axb(modp) 的最小非负整数解,其中 p ∈ p r i m e pin prime pprime

    t = p t=sqrt p t=p x = i t − j x=it-j x=itj。(因为 p ∈ p r i m e pin prime pprime,所以 0 ≤ x ≤ p − 1 0leq xleq 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 ) egin{aligned} a^x&equiv b pmod p\ a^{it-j}&equiv bpmod p\ a^{it}&equiv b imes a^j pmod p end{aligned} axaitjaitb(modp)b(modp)b×aj(modp)

    然后我们先可以 O ( p ) O(sqrt p) O(p ) b × a j b imes 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 imes 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
    ",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
    ",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
    ",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)=sumlimits_P (-1)^{mu(P)}prodlimits_{i=1}^{n}A(i,p_i) det(A)=P(1)μ(P)i=1nA(i,pi)

    其中 P P P 1 ∼ n 1sim n 1n 的一个排列, μ ( P ) mu(P) μ(P) 是排列 P P P 的逆序对数。

    也就是说,我们在矩阵的每一行选一个数(它们所在的列互不相同),然后乘起来。对于每一种选数方案,赋予它一个与逆序对有关的系数,再加起来。

    矩阵树定理有以下的性质:

    1. 交换矩阵的两行,行列式变号。

      也就等同于交换一个 1 ∼ n 1sim n 1n 排列的两个数,整个序列改变的逆序对数为奇数。

      证明:

      设交换的这两个数位置是在 i i i j j j 位置,分别是 x x x y y y

      我们把序列分成三段, A A A 段为 1 ∼ ( i − 1 ) 1sim (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 ∣ egin{vmatrix} a+a'&b+b'\ c&dend{vmatrix} =egin{vmatrix} a&b\ c&dend{vmatrix} +egin{vmatrix} a'&b'\ c&d end{vmatrix} a+acb+bd=acbd+acbd

    4. 若矩阵中某两行相同,则它的行列式为 0 0 0

    5. 用矩阵的一行加上另一行的倍数,行列式不变。

    其中上面的第 2 ∼ 4 2sim 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
    ",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 pin prime pprime,肯定有 φ ( p ) = p − 1 varphi(p)=p-1 φ(p)=p1

      然后对于 p ∈ p r i m e pin 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)= egin{cases} 1&n=1\ (-1)^k& ext{$n$ 无大于 $1$ 的平方数因数,$n$ 分解质因数为 $n=p_1p_2cdots p_k$}\ 0 & ext{$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)=sumlimits_{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 ] sumlimits_{d|n}mu(d)=[n=1] dnμ(d)=[n=1]

      证明:

      n = 1 n=1 n=1,则易得原式等于 1 1 1

      n ≠ 1 n eq 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_ileq 1 xi1

        那么这部分的 a n s ans ans 为:

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

        其中第一个 ∑ i = 0 m sumlimits_{i=0}^m i=0m 枚举所有的 x j x_j xj 中( 1 ≤ j ≤ m 1leq 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 1leq 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 ) mequiv 1pmod 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 ) mequiv 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 egin{aligned} ans=&inom{m}{0}-inom{m}{1}+inom{m}{2}-cdots-inom{m}{m-1}+inom{m}{m}\ =&inom{m-1}{0}-left[inom{m-1}{0}+inom{m-1}{1} ight]+left[inom{m-1}{1}+inom{m-1}{2} ight]-cdots\ &-left[inom{m-1}{m-2}+inom{m-1}{m-1} ight]+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 eq 1 n=1 时,原式为 0 0 0

      证毕。

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

      证明:

      首先把式子转换一下: ∑ d ∣ n φ ( d ) = ∑ d ∣ n φ ( n d ) sumlimits_{d|n}varphi(d)=sumlimits_{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 ) sumlimits_{d|n}varphi(dfrac{n}{d}) dnφ(dn) 就可以看成所有小于等于 n n n 的正整数个数,得 ∑ d ∣ n φ ( n d ) = n sumlimits_{d|n}varphi(dfrac{n}{d})=n dnφ(dn)=n

      证毕。

    3. μ ∗ i d = φ mu*id=varphi μid=φ,即 ∑ d ∣ n μ ( d ) × d = φ ( n ) sumlimits_{d|n}mu(d) imes 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)=sumlimits_{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)=sumlimits_{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)=sumlimits_{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 ⌋ ) egin{aligned} &sum_{i=1}^{n}(f*g)(i)\ =&sum_{i=1}^nsum_{d|i}f(d)g(frac{i}{d})\ =&sum_{d=1}^ng(d)sum_{i=1}^{lfloorfrac{n}{d} floor}f(i)\ =&sum_{d=1}^ng(d)Sleft(lfloorfrac{n}{d} floor ight) 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 ⌋ ) egin{aligned} g(1)S(n)&=sum_{d=1}^ng(d)Sleft(lfloorfrac{n}{d} floor ight)-sum_{d=2}^ng(d)Sleft(lfloorfrac{n}{d} floor ight)\ &=sum_{i=1}^{n}(f*g)(i)-sum_{d=2}^ng(d)Sleft(lfloorfrac{n}{d} floor ight) 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 ) sumlimits_{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
    ",getsumphi(n),getsummu(n));
    	}
    	return 0;
    }
    

    例题

    【BZOJ4916】神犇和蒟蒻

    首先根据莫比乌斯函数的定义,第一问答案一定是 1 1 1

    然后考虑第二问:

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

    f ( n ) = φ ( n ) × n f(n)=varphi(n) imes 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)Sleft(lfloorfrac{n}{d} floor ight) 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 ) sumlimits_{i=1}^{n}(f*g)(i)=sumlimits_{i=1}^n sum_{d|i}varphi(d) imes d imes 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 egin{aligned} sumlimits_{i=1}^{n}(f*g)(i)&=sumlimits_{i=1}^n sum_{d|i}varphi(d) imes d imes g(dfrac{i}{d})\ &=sumlimits_{i=1}^n sum_{d|i}varphi(d) imes d imes dfrac{i}{d}\ &=sumlimits_{i=1}^n sum_{d|i}varphi(d) imes i\ &=sumlimits_{i=1}^n isum_{d|i}varphi(d)\ &=sumlimits_{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
    %lld",dfs(n));
        return 0;
    }
    

    [SDOI2015]约数个数和/【BZOJ4176】Lucas的数论(前者是弱化版)

    ∑ i = 1 n ∑ j = 1 m d ( i j ) sumlimits_{i=1}^nsumlimits_{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)=sumlimits_{x|i}sumlimits_{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 ⌋ egin{aligned} &sum_{i=1}^nsum_{j=1}^m d(ij)\ =&sum_{i=1}^nsum_{j=1}^msum_{x|i}sum_{y|j}[gcd(x,y)=1]\ =&sum_{x=1}^nsum_{y=1}^m[gcd(x,y)=1]sum_{x|i}sum_{y|j}1\ =&sum_{x=1}^nsum_{y=1}^mlfloorfrac{n}{x} floorlfloorfrac{m}{y} floor[gcd(x,y)=1]\ =&sum_{x=1}^nsum_{y=1}^mlfloorfrac{n}{x} floorlfloorfrac{m}{y} floorsum_{d|gcd(x,y)}mu(d)\ =&sum_{d=1}^nmu(d)sum_{d|x}lfloorfrac{n}{x} floorsum_{d|y}lfloorfrac{m}{y} floor\ =&sum_{d=1}^nmu(d)sum_{a=1}^{lfloorfrac{n}{d} floor}lfloorfrac{n}{ad} floorsum_{b=1}^{lfloorfrac{m}{d} floor}lfloorfrac{m}{bd} floor 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,nleq 5 imes 10^4 T,n5×104

    O ( n ) O(n) O(n) 预处理 μ mu μ O ( n n ) O(nsqrt{n}) O(nn ) 预处理 g ( n ) = ∑ i = 1 n ⌊ n i ⌋ g(n)=sumlimits_{i=1}^n lfloordfrac{n}{i} floor g(n)=i=1nin

    然后 O ( T n ) O(Tsqrt{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
    ",ans);
    	}
    	return 0;
    }
    

    强化版: T = 1 , n ≤ 1 0 9 T=1,nleq 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
    ",ans);
        return 0;
    }
    

    多项式

    原根

    定义:若 g φ ( p ) ≡ 1 ( m o d p ) g^{varphi(p)}equiv 1pmod{p} gφ(p)1(modp),且 φ ( p ) varphi(p) φ(p) g x ≡ 1 ( m o d p ) g^xequiv1pmod{p} gx1(modp) 的最小正整数解,则称 g g g p p p 的原根。

    若有 p ∈ p r i m e pin prime pprime,则 φ ( p ) = p − 1 varphi(p)=p-1 φ(p)=p1,那么 g p − 1 ≡ 1 ( m o d p ) g^{p-1}equiv 1pmod{p} gp11(modp),那么有 g 0   m o d   p , g 1   m o d   p , ⋯   , g p − 2   m o d   p g^0mod p,g^1mod p,cdots,g^{p-2}mod p g0modp,g1modp,,gp2modp 1 ∼ ( p − 1 ) 1sim (p-1) 1(p1) 一一映射。(不一定 g i g^i gi i i i 对应)

    证明:

    首先对于 0 ≤ i ≤ p − 2 0leq ileq p-2 0ip2,肯定有 g i   m o d   p ∈ [ 1 , p − 1 ] g^imod pin[1,p-1] gimodp[1,p1]

    因为如果 g i ≡ 0 ( m o d p ) g^iequiv 0pmod p gi0(modp),那么对于 j ≥ i jgeq i ji,肯定有 g j ≡ 0 ( m o d p ) g^jequiv 0pmod{p} gj0(modp),那么 g φ ( p ) ≡ 1 ( m o d p ) g^{varphi(p)}equiv 1pmod{p} gφ(p)1(modp) 肯定不成立。

    然后若存在 g i ≡ g j ( m o d p ) g^iequiv g^jpmod p gigj(modp) 0 ≤ j < i ≤ p − 2 0leq j<ileq p-2 0j<ip2),那么 g i − j ≡ 1 ( m o d p ) g^{i-j}equiv 1pmod 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^0mod p,g^1mod p,cdots,g^{p-2}mod p g0modp,g1modp,,gp2modp 1 ∼ ( p − 1 ) 1sim (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)equiv1pmod {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)mod 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^{leftlceilfrac{n}{2} ight ceil}} 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^{leftlceilfrac{n}{2} ight ceil}} 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) ight)equiv 0pmod{x^{leftlceilfrac{n}{2} ight ceil}} F(x)(H(x)G(x))0(modx2n)

    由于 F ( x ) ≢ 0 ( m o d x ⌈ n 2 ⌉ ) F(x) otequiv 0pmod{x^{leftlceilfrac{n}{2} ight ceil}} F(x)0(modx2n),所以 H ( x ) − G ( x ) ≡ 0 ( m o d x ⌈ n 2 ⌉ ) H(x)-G(x)equiv 0 pmod{x^{leftlceilfrac{n}{2} ight ceil}} 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 leftlceildfrac{n}{2} ight ceil-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 0pmod{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 ) egin{aligned} F(x)H(x)^2+F(x)G(x)^2-2F(x)G(x)H(x)&equiv 0pmod{x^n}\ F(x)H(x)^2+G(x)-2H(x)&equiv 0pmod{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^{leftlceilfrac{n}{2} ight ceil}} F(x)H(x)1(modx2n),因为两个式子   m o d   mod 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 ⌉ leftlceildfrac{n}{2} ight ceil 2n 的答案求出 n n n 时的答案了,所以一直向下递归。

    最后当递归到 1 1 1 的时候,要求 F ( x ) G ( x ) ≡ 1 ( m o d x 1 ) F(x)G(x)equiv 1pmod{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 sumlimits_{i=0}^{m-1}2^ilog(2^i)<sumlimits_{i=0}^{m-1}2^ilog(n)=left(2^{m-1+1}-1 ight)log(n)=(n-1)log(n)<nlog 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)^2equiv F(x)pmod{x^n} G(x)2F(x)(modxn),已经求出了 H ( x ) 2 ≡ F ( x ) ( m o d x ⌈ n 2 ⌉ ) H(x)^2equiv F(x)pmod{x^{leftlceilfrac{n}{2} ight ceil}} H(x)2F(x)(modx2n)。(记为 1 1 1 式)

    显然若 G ( x ) 2 ≡ F ( x ) ( m o d x n ) G(x)^2equiv 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^{leftlceilfrac{n}{2} ight ceil}} 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 ) egin{aligned} G(x)&equiv H(x)pmod{x^{leftlceilfrac{n}{2} ight ceil}}\ G(x)-H(x)&equiv 0pmod{x^{leftlceilfrac{n}{2} ight ceil}}\ (G(x)-H(x))^2&equiv 0pmod{x^n}\ G(x)^2+H(x)^2-2G(x)H(x)&equiv 0pmod{x^n}\ F(x)+H(x)^2-2G(x)H(x)&equiv 0pmod{x^n}\ G(x)&equivfrac{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(nlog 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)=sumlimits_{igeq0}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 egin{aligned} f'(x)=&lim_{d ightarrow 0}sum_{igeq0}frac{a_i(x+d)^i-a_ix^i}{d}\ =&lim_{d ightarrow 0}sum_{igeq0}a_ifrac{(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 egin{aligned} &frac{(x+d)^i-x^i}{d}\ =&frac{sumlimits_{j=0}^idbinom{i}{j}x^jd^{i-j}-x^i}{d}\ =&frac{sumlimits_{j=0}^{i-1}dbinom{i}{j}x^jd^{i-j}+x^id^0-x^i}{d}\ =&frac{sumlimits_{j=0}^{i-1}dbinom{i}{j}x^jd^{i-j}}{d}\ =&sumlimits_{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 sumlimits_{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 egin{aligned} f'(x)=&lim_{d ightarrow 0}sum_{igeq0}a_ifrac{(x+d)^i-x^i}{d}\ =&sum_{igeq 1}a_iix^{i-1} end{aligned} f(x)==d0limi0aid(x+d)ixii1aiixi1

    同样的,若知道 f ′ ( x ) = ∑ i ≥ 0 a i x i f'(x)=sumlimits_{igeq 0}a_ix^i f(x)=i0aixi,那么 f ( x ) = ∑ i ≥ 1 a i − 1 i x i f(x)=sumlimits_{igeq 1}dfrac{a_{i-1}}{i}x^i f(x)=i1iai1xi

    一些特殊形式的求法

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

    平常的 FFT 是求 ∑ k = 1 n x k ∑ i + j = k a i b j sumlimits_{k=1}^{n}x^ksumlimits_{i+j=k}a_ib_j k=1nxki+j=kaibj,那么我现在让你求 ∑ k = 1 n x k ∑ i − j = k a i b j sumlimits_{k=1}^{n}x^ksumlimits_{i-j=k}a_ib_j k=1nxkij=kaibj

    S ( k ) = ∑ i − j = k a i b j S(k)=sumlimits_{i-j=k}a_ib_j S(k)=ij=kaibj,那么题目是要求 ∑ k = 1 n x k S ( k ) sumlimits_{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 egin{aligned} S(k)=&sumlimits_{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 imes 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^0mod m,g^1mod m,cdots,g^{m-2}mod m g0modm,g1modm,,gm2modm 1 ∼ ( m − 1 ) 1sim (m-1) 1(m1) 一一映射,所以考虑用 g 0   m o d   m , g 1   m o d   m , ⋯   , g m − 2   m o d   m g^0mod m,g^1mod m,cdots,g^{m-2}mod m g0modm,g1modm,,gm2modm 代替 1 ∼ ( m − 1 ) 1sim (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
    ",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 eq 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 eq 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_iell_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
    ",(ans+mod)%mod);
    	return 0;
    }
    
  • 相关阅读:
    Java实现Excel导入数据库,数据库中的数据导入到Excel
    MySQL如何把A表查询出来的某个字段的数据插入到新增的字段的下面
    MySQL怎么把小数转换为百分比?
    linux上安装python3和pip----最简单的安装
    linux pip 安装包的时候报错:Could not find a version that satisfies the requirement bs4 (from versions: ) No matching distribution found for bs4
    Excel提取中文
    关于Excel的一些小技巧
    5-24 树种统计 (25分)
    POJ 2663 Tri Tiling
    5-3 树的同构 (25分)
  • 原文地址:https://www.cnblogs.com/ez-lcw/p/14448615.html
Copyright © 2011-2022 走看看