单位根
举例来说,我们想计算下面这个式子的值:
那么,我们构造二项式:
带入
和
,我们得到:
把这两个式子加起来:
由此可以得到:
那么,一个类似的问题就是,如果想计算:
应该怎么办呢?
在实数域上,你找不到一个和 +/- 1 有一样好的性质的数来帮你反演。
单位根
的解 称为一个 次单位根。
对于一个单位根 ,如果 是使得 成立的最小正整数,那么 称为一个 次本原单位根。
Remarks:如果在复平面上看,单位根均匀地分布在单位圆上,设第一象限第一个单位根为 ,那么其他的单位根都可以写为 的形式。如果 ,那么 ,而 是比 更小的一个正整数,所以 不是n次本原单位根。
性质:
证明:
如果 ,那么是 个 1 加起来。
否则它是等比数列求和,值为:
Remarks:当 n 是偶数的时候,这是 和 相互消掉的过程。
回到上一个话题,想计算:
那么,令 为一个 3 次本原多项式,构造多项式:
考虑
,我们发现,对于
来说,它的系数是:
所以, .
大家应该注意到,这个算法扩展性很强:
任给一个生成函数 ,你想知道它的所有 k 倍数次项系数之和,可以转化为代入 k 个单位根,求平均值。
eg1 (bzoj 3328)
n 比较大,k 是几千左右,模数 P 下存在 k 次单位根。
单位根的求法:求出 P 的一个原根 g,g 可以认为是一个 P-1 次本原单位根, 就是 次本原单位根。
Sol
设 Fib 数的转移矩阵为 ,构造 ,其中 的系数恰好为 ,(因为 I 和任意矩阵可交换,所以二项式定理适用)。把 中的左上角元素取出来就是 .为了只算 k 倍数,令 ,然后带入单位根求值即可。
#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 记录了个数。
问题相当于求 项中,所有 n 倍数的 x 次幂 系数之和。
我们考虑用单位根反演它,带入单位根
:
设 是一个 次本原单位根 (d|n)(这里 w 会取遍所有 n 的单位根,不一定是 n 次本原,设它是 d 次本原的)
这里有这样一个式子成立:
证明:因为两边是两个 d 次多项式,并且零点/首项系数相同。
我们在两边乘上一个
,可以得到
,把公式带回原式,得到:
这时候算 的系数就很容易了。
复杂度:我们只需要枚举所有 的因子,有 个 次单位根是 次本原的。计算组合数可以暴力,复杂度很优秀。
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 里的所有质数取出来,设为
,设
的形式为:
对于一个阶乘 ,我们可以计算出每个质数在 的分解中的次数。
注意到这样一个事实:(x-1)! 相比于 x!,它只在 个质因子上次数发生了改变。
考虑线段树,我们对所有质数 建线段树,线段树每个位置存值:n 的质因子分解中 的次数 , 的分解中 的次数 ,以及 的值。
那么,计算 n 的分解中 出现了多少次,相当于询问 的最小值。为了在 中把 除掉,我们暴力即可。当从 向 枚举的时候,暴力修改 个位置上的信息即可。
如果打标记,复杂度应该是 . 因为只有 能整除 才会修改,实际上和埃拉托斯特尼筛法复杂度一致。
2 (51nod 1325)
枚举一个根必选,然后建两个有根树,有根树上选儿子意味着选祖先,问题转化为最大权闭合图。
如果使用点分治,可以把枚举根的复杂度降一些。
3 (52nod 1355)
公式:
它来源于:
Fib 数又有一个公式:
那么我们所求即为:
我们类比加法形式的:
在加法形式中,这是经典的莫比乌斯反演问题,它的一系列推导手段都可以推广到乘法上。
反演套路:找到一个
使得:
那么:
具体计算过程中,因为权值范围很小,正确实现的复杂度为 即可通过。
4 (bzoj 4588)
我们把所有货币从小到大排序,那么它按照整除关系形成一个链。
我们考虑从前向后枚举每种硬币进行DP。
假设排好序的硬币序列是 , 不妨设 .
我们设
表示用
拼出
加上
的零散部分的方案数,那么:
考虑从
向
的转移,首先我们知道
对
的余数
应该由
提供,除此之外,
的使用次数应该是
的整倍数,设:
枚举 使用了多少个:
然后我们使用归纳法可以证明 是一个次数不超过 的多项式,所以你只需要维护多项式的 (i+1) 个点值 / 系数表达 / 组合数表达 即可.
州阁筛
问题: 求 , 它表示不超过 的素数个数, .
考虑去 DP / 筛它,我们把不超过 的素数取出来,设为 .
设 表示在 中不被 中任何一个整除的数的个数.
我们发现, 即为所求答案.
转移:
首先用 p1 ~ p[k-1] 筛,然后用 pk 筛漏掉的.
一些数论常识告诉我们,dp的第二维有用的状态只有 个.
直接使用这个算法进行dp,复杂度是 并不能通过.
注意到:
当 时,一定有 .
当 时, ,所以转移式中 ,发现转移的第二项是定值.
所以我们枚举到 的时候,只需要关注 的状态的转移, 这些状态的转移可以打懒标记,最后统一处理.
加上这个优化,复杂度可以降到 ,可以接受.
这是一个通用的算法,也适用于求关于素数的低阶多项式的和 (和,平方和…)
5 (51nod 1860)
问题相当于求 [a,b] 里有多少数可以由 <= P 的质数表示
进一步转化,相当于求 [1,a-1] 和 [1,b] 里由多少数可以这样表示.
如果一个数 n 可以由不超过 B 的数的乘积表示,称它为 B-smooth number ,B-光滑数
我们把 1~P 里的质数拿出来,设为 .
我们设
表示
有多少数能表示为
的倍数,转移:
直接做的复杂度是 ,能优化吗?
从转移式中看不出一些好用的性质,考虑倒着 DP.
设
表示
中
的乘积能表示的数的个数,转移:
注意到:
当 的时, .
当 时, .
发现了和例题中一样的性质,然后套路就行了.
Remark: 正着做的话,小质数很快的就能表示出区间里很多数,并且这些数的分布的规律可能并不好找. 倒着做的话,较大的质数表出的区间中的数比较稀疏,所以可以优化.
before
幂和
求
Sol
三方做法:
构造一个向量
发现可以通过 线性表示出
所以可以构造转移矩阵
平方做法:
C(1, k) + C(2, k) + … + C(n, k) = C(n+1, k+1)
我们把 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) 一项去掉
。
可以注意到这个 f_i(x) 是一个 k 次多项式(分子上乘了 k 个 x)
在很多场合,题目并不需要你去计算这个多项式的系数,而是要求你把某个 f(n) 算出来(已经给你 f(0) ~ f(k))的值。
考虑直接把 n 带入 求值,然后加起来即可。
注意 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[]
求
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,哪个多猜哪个是比较优秀的(得分概率为 )。
序列是随机生成的,所以你会转移到一个剩下 x 个 1 和 y 个 0 的局面的方案数为
答案为 ,但是这个式子的枚举量是 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 需要消掉即可。
威尔逊定理
我们从多项式的观点看,费马小定理告诉我们:
那么我们知道, 都是 的根(在 mod P 下)。又因为 是一个首项为 1 的 P-1 次多项式,所以有:
比较两个多项式的系数,由此得到威尔逊定理:
另外一个结论:对于一个给定的质数 ,我们定义 表示从 中选 个不同的数求乘积,所有选法的乘积之和(在模 P 意义下)那么有:
证明:考虑 的 次项系数。
CC Feb / Jan
给定N,定义 f(K) 为从 [1,N] 中选K个不同的整数求乘积,不同的选法的乘积之和
给定R,求 [1,R] 中有多少f(K)是给定质数P的倍数
Sol
问题相当于求 在 中有多少项为 0(或者说有多少项非 0)。
设
,那么原式可以写为:
因为 等于 ,所以上式可以写成:
不妨设 ,否则右边又能凑出一个 。
注意到上式左边只有 的整倍数有值,而右边是一个次数为 的多项式,所以右边乘到左边相当于把左边每个系数“复制”了若干份。
右边的多项式可以使用分治 FFT 展开计算,左边的式子可以用 Lucas 定理统计有多少非 0 项,然后两边结合即可。
Catalan 数
Catalan 数定义:满足递推公式
的数列 。
推导方法
生成函数方法
设它的生成函数为
,它满足:
解得:
使用二项式定理展开 :
思路: 拆成 ,一个 消掉之后分母上所有 2,一个 展开成 (2/1 * 4/2 * 6/3 …),从而我们可以在分子分母上构造出来 ,注意到前面有 个 -1,之后又有 个 -1,放在一起以后每一项都是负的。
组合方法
组合意义
- N 条边的凸多边形三角剖分方案数
- N 个点的二叉树的形态数
- N 对括号的合法括号序列数
- 1~N 依次入栈,可能的出栈序列数
两种形式
- 维护了一个计数器,每次给他加一或者减一,不能为负/低于某个值,这一类问题常常可以用括号序计数的角度思考
- 对一个规模为N的问题计数,它可以拆成两个规模为x和 N-x-1的问题,这一类问题常可用二叉树形态计数的角度思考,配合生成函数的卷积。