单位根
举例来说,我们想计算下面这个式子的值:
那么,我们构造二项式:
带入
x=1
x
=
1
和
x=−1
x
=
−
1
,我们得到:
把这两个式子加起来:
由此可以得到:
那么,一个类似的问题就是,如果想计算:
应该怎么办呢?
在实数域上,你找不到一个和 +/- 1 有一样好的性质的数来帮你反演。
单位根
xn−1=0 x n − 1 = 0 的解 w w 称为一个 次单位根。
对于一个单位根 w w ,如果 是使得 wn−1=0 w n − 1 = 0 成立的最小正整数,那么 w w 称为一个 次本原单位根。
Remarks:如果在复平面上看,单位根均匀地分布在单位圆上,设第一象限第一个单位根为 w w ,那么其他的单位根都可以写为 的形式。如果 (i,n)=d ( i , n ) = d ,那么 wi(n/d)=wn=1 w i ( n / d ) = w n = 1 ,而 n/d n / d 是比 n n 更小的一个正整数,所以 不是n次本原单位根。
性质:
证明:
如果 n|k n | k ,那么是 n n 个 1 加起来。
否则它是等比数列求和,值为:
Remarks:当 n 是偶数的时候,这是 wi w i 和 wi+n/2 w i + n / 2 相互消掉的过程。
回到上一个话题,想计算:
那么,令 w w 为一个 3 次本原多项式,构造多项式:
考虑
f(1)+f(w)+f(w2)
f
(
1
)
+
f
(
w
)
+
f
(
w
2
)
,我们发现,对于
(ni)
(
n
i
)
来说,它的系数是:
所以, 13(f(1)+f(w)+f(w2))=ans 1 3 ( f ( 1 ) + f ( w ) + f ( w 2 ) ) = a n s .
大家应该注意到,这个算法扩展性很强:
任给一个生成函数 f(x) f ( x ) ,你想知道它的所有 k 倍数次项系数之和,可以转化为代入 k 个单位根,求平均值。
eg1 (bzoj 3328)
n 比较大,k 是几千左右,模数 P 下存在 k 次单位根。
单位根的求法:求出 P 的一个原根 g,g 可以认为是一个 P-1 次本原单位根, g(P−1)/k g ( P − 1 ) / k 就是 k k 次本原单位根。
Sol
设 Fib 数的转移矩阵为 ,构造 (I+M)n ( I + M ) n ,其中 Mj M j 的系数恰好为 (nj)Mj ( n j ) M j ,(因为 I 和任意矩阵可交换,所以二项式定理适用)。把 Mj M j 中的左上角元素取出来就是 fibj f i b j .为了只算 k 倍数,令 f(x)=(I+xM)n f ( x ) = ( I + x M ) n ,然后带入单位根求值即可。
#include<cstdio>
#include<iostream>
using namespace std;
struct Ma{
long long a[5][5];
};
int T,k,p,prime[10000],cnt;
long long n;
Ma x(Ma a,Ma b)
{
Ma ans;
for(int i=1;i<=2;i++)
for(int j=1;j<=2;j++)
{
ans.a[i][j]=0;
for(int k=1;k<=2;k++)
ans.a[i][j]=(ans.a[i][j]+a.a[i][k]*b.a[k][j])%p;
}
return ans;
}
Ma M_Pow(Ma a,long long b)
{
Ma ans=a,base=a;
b--;
while(b)
{
if(b&1) ans=x(ans,base);
base=x(base,base);
b>>=1;
}
return ans;
}
long long Pow(int a,int b)
{
long long ans=1,base=a%p;
while(b)
{
if(b&1) ans=ans*base%p;
b>>=1;
base=base*base%p;
}
return ans;
}
bool check(int g)
{
for(int i=1;i<=cnt;i++)
if(Pow(g,(p-1)/prime[i])==1) return false;
return true;
}
int get_root(int p,int phi)
{
for(int i=2;i<=phi;i++)
{
if(phi%i==0)
{
prime[++cnt]=i,phi/=i;
while(phi%i==0) phi/=i;
}
}
if(phi!=1) prime[++cnt]=phi;
int g=2;
while(g<p)
{
if(check(g)) return g;
g++;
}
}
long long F(int x)
{
Ma tmp;
tmp.a[1][1]=tmp.a[1][2]=tmp.a[2][1]=x;
tmp.a[2][2]=0;
tmp.a[1][1]++,tmp.a[2][2]++;
tmp=M_Pow(tmp,n);
return (tmp.a[1][1]%p+p)%p;
}
int main()
{
scanf("%d",&T);
while(T--)
{
cnt=0;
scanf("%lld%d%d",&n,&k,&p);
int W=Pow(get_root(p,p-1),(p-1)/k);
long long w=1,ans=0;
for(int i=0;i<k;i++)
{
ans=(ans+F(w))%p;
w=w*W%p;
}
ans=(ans*Pow(k,p-2)%p+p)%p;
printf("%lld\n",ans);
}
return 0;
}
eg2
从 0 ~ (N-1) 中选出 K 个不同的数使得它们的和是 N 的倍数的方案数
K<=1000, N<=10^9, mod = 10^9+7
Sol
我们设:
注意到 x 的次数记录了选的数的和,y 记录了个数。
问题相当于求 yk y k 项中,所有 n 倍数的 x 次幂 系数之和。
我们考虑用单位根反演它,带入单位根
w
w
:
设 w w 是一个 次本原单位根 (d|n)(这里 w 会取遍所有 n 的单位根,不一定是 n 次本原,设它是 d 次本原的)
这里有这样一个式子成立:
证明:因为两边是两个 d 次多项式,并且零点/首项系数相同。
我们在两边乘上一个
wXXX
w
X
X
X
,可以得到
∏d−1i=0(wix−1)=xd−1
∏
i
=
0
d
−
1
(
w
i
x
−
1
)
=
x
d
−
1
,把公式带回原式,得到:
这时候算 yk y k 的系数就很容易了。
复杂度:我们只需要枚举所有 n n 的因子,有 个 n n 次单位根是 次本原的。计算组合数可以暴力,复杂度很优秀。
EXT:选 K 个使得模 n 余 r 成立的方案数。
杂题
1 (51nod 1982)
考虑逐位确定,对给定的 N,从大到小枚举阶乘,然后试除,大概过程是:
for (int i = 100000; i >= 2; i--) {
while (N % fact(i)) {
N /= fact(i);
ans.push(fact(i));
}
}
注意到过程中没有加减法运算,那么我们可以用质因子分解来判整除。
我们把 1~N 里的所有质数取出来,设为
b1…bk
b
1
…
b
k
,设
n
n
的形式为:
对于一个阶乘 x! x ! ,我们可以计算出每个质数在 x! x ! 的分解中的次数。
注意到这样一个事实:(x-1)! 相比于 x!,它只在 O(logx) O ( log x ) 个质因子上次数发生了改变。
考虑线段树,我们对所有质数 b1…bk b 1 … b k 建线段树,线段树每个位置存值:n 的质因子分解中 bi b i 的次数 ni n i , x! x ! 的分解中 bi b i 的次数 xi x i ,以及 ⌊nixi⌋ ⌊ n i x i ⌋ 的值。
那么,计算 n 的分解中 x! x ! 出现了多少次,相当于询问 [ni/xi] [ n i / x i ] 的最小值。为了在 n n 中把 除掉,我们暴力即可。当从 x! x ! 向 (x−1)! ( x − 1 ) ! 枚举的时候,暴力修改 logx log x 个位置上的信息即可。
如果打标记,复杂度应该是 O(nlognloglogn) O ( n log n log log n ) . 因为只有 x x 能整除 才会修改,实际上和埃拉托斯特尼筛法复杂度一致。
2 (51nod 1325)
枚举一个根必选,然后建两个有根树,有根树上选儿子意味着选祖先,问题转化为最大权闭合图。
如果使用点分治,可以把枚举根的复杂度降一些。
3 (52nod 1355)
公式:
它来源于:
Fib 数又有一个公式:
那么我们所求即为:
我们类比加法形式的:
在加法形式中,这是经典的莫比乌斯反演问题,它的一系列推导手段都可以推广到乘法上。
反演套路:找到一个
g
g
使得:
那么:
具体计算过程中,因为权值范围很小,正确实现的复杂度为 O(NlogN) O ( N log N ) 即可通过。
4 (bzoj 4588)
我们把所有货币从小到大排序,那么它按照整除关系形成一个链。
我们考虑从前向后枚举每种硬币进行DP。
假设排好序的硬币序列是 vi v i , 不妨设 v1=1 v 1 = 1 .
我们设
fi(n)
f
i
(
n
)
表示用
v1∼vi
v
1
∼
v
i
拼出
n×vi
n
×
v
i
加上
Mmodvi
M
mod
v
i
的零散部分的方案数,那么:
考虑从
1
1
向 的转移,首先我们知道
M
M
对 的余数
r
r
应该由 提供,除此之外,
v1
v
1
的使用次数应该是
d=v2/v1
d
=
v
2
/
v
1
的整倍数,设:
枚举 v2 v 2 使用了多少个:
然后我们使用归纳法可以证明 fi(n) f i ( n ) 是一个次数不超过 i i 的多项式,所以你只需要维护多项式的 (i+1) 个点值 / 系数表达 / 组合数表达 即可.
州阁筛
问题: 求 , 它表示不超过 n n 的素数个数, .
考虑去 DP / 筛它,我们把不超过 n−−√ n 的素数取出来,设为 p1,p2…pk p 1 , p 2 … p k .
设 fi(n) f i ( n ) 表示在 [1,n] [ 1 , n ] 中不被 p1∼pi p 1 ∼ p i 中任何一个整除的数的个数.
我们发现, fk(n)+k f k ( n ) + k 即为所求答案.
转移:
首先用 p1 ~ p[k-1] 筛,然后用 pk 筛漏掉的.
一些数论常识告诉我们,dp的第二维有用的状态只有 n−−√ n 个.
直接使用这个算法进行dp,复杂度是 O(nlogn) O ( n log n ) 并不能通过.
注意到:
当 n<pi+1 n < p i + 1 时,一定有 fi(n)=1 f i ( n ) = 1 .
当 n<(pi+1)2 n < ( p i + 1 ) 2 时, [n/pi+1]<pi+1 [ n / p i + 1 ] < p i + 1 ,所以转移式中 fi+1(n)=fi(n)−fi([n/pi+1])=fi(n)−1 f i + 1 ( n ) = f i ( n ) − f i ( [ n / p i + 1 ] ) = f i ( n ) − 1 ,发现转移的第二项是定值.
所以我们枚举到 pi p i 的时候,只需要关注 n>(pi)2 n > ( p i ) 2 的状态的转移, pi≤n<(pi)2 p i ≤ n < ( p i ) 2 这些状态的转移可以打懒标记,最后统一处理.
加上这个优化,复杂度可以降到 O(n3/4logn) O ( n 3 / 4 log n ) ,可以接受.
这是一个通用的算法,也适用于求关于素数的低阶多项式的和 (和,平方和…)
5 (51nod 1860)
问题相当于求 [a,b] 里有多少数可以由 <= P 的质数表示
进一步转化,相当于求 [1,a-1] 和 [1,b] 里由多少数可以这样表示.
如果一个数 n 可以由不超过 B 的数的乘积表示,称它为 B-smooth number ,B-光滑数
我们把 1~P 里的质数拿出来,设为 p1,p2,…,pk p 1 , p 2 , … , p k .
我们设
fi(n)
f
i
(
n
)
表示
[1,n]
[
1
,
n
]
有多少数能表示为
p1,p2…pi
p
1
,
p
2
…
p
i
的倍数,转移:
直接做的复杂度是 O(PN−−√) O ( P N ) ,能优化吗?
从转移式中看不出一些好用的性质,考虑倒着 DP.
设
gi(n)
g
i
(
n
)
表示
[1,n]
[
1
,
n
]
中
pi,pi+1…pk
p
i
,
p
i
+
1
…
p
k
的乘积能表示的数的个数,转移:
注意到:
当 n<pi n < p i 的时, gi(n)=1 g i ( n ) = 1 .
当 pi≤n<(pi)2 p i ≤ n < ( p i ) 2 时, gi(n)=gi+1(n)+gi([n/pi])=gi+1(n)+1 g i ( n ) = g i + 1 ( n ) + g i ( [ n / p i ] ) = g i + 1 ( n ) + 1 .
发现了和例题中一样的性质,然后套路就行了.
Remark: 正着做的话,小质数很快的就能表示出区间里很多数,并且这些数的分布的规律可能并不好找. 倒着做的话,较大的质数表出的区间中的数比较稀疏,所以可以优化.
before
幂和
求
Sol
三方做法:
构造一个向量 A=(i0,i1,...ik,SUM). A = ( i 0 , i 1 , . . . i k , S U M ) .
发现可以通过 i0…ik i 0 … i k 线性表示出 (i+1)k. ( i + 1 ) k .
所以可以构造转移矩阵
平方做法:
C(1, k) + C(2, k) + … + C(n, k) = C(n+1, k+1)
我们把 i^k 看作一个多项式,我们可以用 (i0)…(ik) ( i 0 ) … ( i k ) 来表示这个多项式。(它的表示系数恰好是斯特林数)。
n log n 算法
斯特林数可以使用 FFT 来预处理
线性算法
我们可以猜想,答案是一个关于 n 的 k+1 次多项式。
( k 次多项式,给它求前缀和,可以得到一个 k+1 次多项式)。
给定一个 k 次多项式 f(x) 在 0 ~ k 处的取值 (a[0~k]),我们可以使用拉格朗日插值法,把这个多项式算出来。
代数上的知识:给定一个多项式在 k+1 个点的取值,可以唯一决定一个 k 次多项式,所以我们只需要构造多项式 f,使得 f(i) = a[i].
类比中国剩余定理的做法,我们考虑:
它的分母相当于分子带入 x=i 得到的一个式子。
它的分子相当于在 (x-0)(x-1)…(x-k) 中把 (x-i) 一项去掉
j≠i,fi(j)=0(0<=j<=k)fi(i)=a[i] j ≠ i , f i ( j ) = 0 ( 0 <= j <= k ) f i ( i ) = a [ i ] 。
可以注意到这个 f_i(x) 是一个 k 次多项式(分子上乘了 k 个 x)
f(x)=∑fi(x) f ( x ) = ∑ f i ( x )
在很多场合,题目并不需要你去计算这个多项式的系数,而是要求你把某个 f(n) 算出来(已经给你 f(0) ~ f(k))的值。
考虑直接把 n 带入 fi(x) f i ( x ) 求值,然后加起来即可。
注意 42 ~ 44 行的式子:
- 分母上是一个 (i-1)! 和一个 (k-i)! ,乘上若干个 -1 (O(1)算)。
- 分子上的话,考虑在 O(k) 的时间内预处理 (n-0) ~ (n-k) 的前缀积,后缀积,那么对于一个 f_i(n) 来说,就是一个前缀积和后缀积的乘积(除掉了 n-i 这一项) O(1) 算
这整个式子可以在 O(k) 计算,从而得到 f(n) 的值。
应用到这个题上,我们可以在 O(k) 的时间内计算出 0^k, 1^k… k^k ,求前缀和得到这个多项式在 0 ~ k 上的取值,然后套算法即可。
总结:当你注意到题目中让你计数的对象是一个关于 n 的 k 次多项式,就可以考虑暴力算出多项式的前 k+1 项,然后套用这个算法。
CodePlus 3 T4
T 次询问,每次求长度为 N+M 的、恰好有 N 个 -1 和 M 个 1 的序列的前缀最小值的期望
N, M <= 200000, T <= 200000
Sol
总方案数是 C(N+M, M)
求一个随即变量 X 的期望,如果 X 的取值范围是整数,我们可以枚举 i,计算 Pr(X>i) 或者 Pr(X
BBQ Hard
给定长度为 N 的数组 A[] 和 B[]
求
∑i∑jC(Ai+Aj+Bi+Bj,Ai+Aj)
∑
i
∑
j
C
(
A
i
+
A
j
+
B
i
+
B
j
,
A
i
+
A
j
)
N <= 200000, A[i], B[i] <= 2000
Sol
所求的每一项相当于从 (-A[i], -B[i]) 移动到 (A[j], B[j]) 的方案数(每一步向右或者上)。
因为 A[i], B[i] 都很小,所以可以直接做一个 4000 * 4000 的 DP。
AGC 019 F
有一个 N+M 的序列,每个位置是 0 或者 1,且恰好有 N 个 1
你需要依次猜每个位置的值,猜对得 1 分,否则不得分。
猜过一个位置,你会马上知道它的真实值
求最优策略下的得分期望
N+M <= 1e6
Sol
最优策略下,设到现在为止剩下 x 个 1 和 y 个 0,哪个多猜哪个是比较优秀的(得分概率为 max(x,y)/(x+y) m a x ( x , y ) / ( x + y ) )。
序列是随机生成的,所以你会转移到一个剩下 x 个 1 和 y 个 0 的局面的方案数为
答案为 ∑x∑y(x+yx)×(N−x+M−yN−x)(N+MN)×max(x,y)x+y ∑ x ∑ y ( x + y x ) × ( N − x + M − y N − x ) ( N + M N ) × max ( x , y ) x + y ,但是这个式子的枚举量是 O(N^2) 的。
我们枚举 len=(x+y),计算倒数第 len 次猜的时候会得分的概率。
- 定义一个局面(或者说一个状态)是被 0 支配的,当且仅当这 len 个数里 0 比 1 多(这时候,max(x,y) 中取 y)
- 定义一个局面是被 1 支配的,当且仅当这 len 个数里 1 的个数大于等于 0(这时候,max(x,y) 中取 x)
我们从小到大枚举 len,从 len 到 len+1 的时候,绝大多数的序列被支配关系是不变的。
所以我们设几个变量,分别表示被 0 支配的序列的个数/产生的贡献和,被 1 支配的序列的个数/产生的贡献和。
从 len 转移到 len+1 的时候,只需要特殊处理被支配关系发生转变的序列(这一部分的计算不需要枚举任何东西,因为只有 0 和 1 的个数差不超过 1 的序列需要考虑)。
具体细节可以参见 AC 代码。
数论基础
- 整数、同余、最大公约数与最小公倍数
- 裴蜀定理,一次不定方程,中国剩余定理
裴蜀定理
形如 ax+by = c 的方程称为二元一次不定方程。
不定方程:限定了未知数只能取整数。
使得 ax+by=d 有解的、最小的正整数 d 恰好是 gcd(a,b)。
这里 x 和 y 是未知数。
ax+by=gcd(a,b) 一定有解,它的解可以通过扩展欧几里得算法找出来。
HAOI 外星人
Sol
对于一个 N,求欧拉函数 phi(N) 的过程:
- 令 ans = N
- 枚举 N 的每种质因子 p,令 ans = ans / p * (p - 1)
注意到:
- 对于 N 的一个质因子 p > 2,ans / p * (p - 1) 虽然消掉了一个 p,但是带来了几个更小的质因子。这个过程会持续下去,直到变成一个 2,然后 ans / 2 可以消掉一个质因子 2 而不产生任何新的质因子。
- 除了 N 是奇数的第一次取 phi,每一次一定会消掉一个 2。
所以我们只需要数一下有多少个 2 需要消掉即可。
威尔逊定理
我们从多项式的观点看,费马小定理告诉我们:
那么我们知道, 1∼P−1 1 ∼ P − 1 都是 xP−1−1 x P − 1 − 1 的根(在 mod P 下)。又因为 xP−1−1 x P − 1 − 1 是一个首项为 1 的 P-1 次多项式,所以有:
比较两个多项式的系数,由此得到威尔逊定理:
另外一个结论:对于一个给定的质数 P P ,我们定义 表示从 1∼P−1 1 ∼ P − 1 中选 k k 个不同的数求乘积,所有选法的乘积之和(在模 P 意义下)那么有:
证明:考虑 ∏1≤i≤P−1(x−i) ∏ 1 ≤ i ≤ P − 1 ( x − i ) 的 k k 次项系数。
CC Feb / Jan
给定N,定义 f(K) 为从 [1,N] 中选K个不同的整数求乘积,不同的选法的乘积之和
给定R,求 [1,R] 中有多少f(K)是给定质数P的倍数
Sol
问题相当于求 在 [1,R] [ 1 , R ] 中有多少项为 0(或者说有多少项非 0)。
设
N=aP+b
N
=
a
P
+
b
,那么原式可以写为:
因为 ∏1≤i≤P−1(x−i) ∏ 1 ≤ i ≤ P − 1 ( x − i ) 等于 xP−1−1 x P − 1 − 1 ,所以上式可以写成:
不妨设 b<P−1 b < P − 1 ,否则右边又能凑出一个 xP−1−1 x P − 1 − 1 。
注意到上式左边只有 P−1 P − 1 的整倍数有值,而右边是一个次数为 b<P−1 b < P − 1 的多项式,所以右边乘到左边相当于把左边每个系数“复制”了若干份。
右边的多项式可以使用分治 FFT 展开计算,左边的式子可以用 Lucas 定理统计有多少非 0 项,然后两边结合即可。
Catalan 数
Catalan 数定义:满足递推公式
的数列 {hn} { h n } 。
推导方法
生成函数方法
设它的生成函数为
C(x)
C
(
x
)
,它满足:
解得:
使用二项式定理展开 1−4x−−−−−√ 1 − 4 x :
思路: 4i 4 i 拆成 2i×2i 2 i × 2 i ,一个 2i 2 i 消掉之后分母上所有 2,一个 2i 2 i 展开成 (2n)!!n! ( 2 n ) ! ! n ! (2/1 * 4/2 * 6/3 …),从而我们可以在分子分母上构造出来 (2nn) ( 2 n n ) ,注意到前面有 i i 个 -1,之后又有 个 -1,放在一起以后每一项都是负的。
组合方法
组合意义
- N 条边的凸多边形三角剖分方案数
- N 个点的二叉树的形态数
- N 对括号的合法括号序列数
- 1~N 依次入栈,可能的出栈序列数
两种形式
- 维护了一个计数器,每次给他加一或者减一,不能为负/低于某个值,这一类问题常常可以用括号序计数的角度思考
- 对一个规模为N的问题计数,它可以拆成两个规模为x和 N-x-1的问题,这一类问题常可用二叉树形态计数的角度思考,配合生成函数的卷积。