计数/数论相关+插值与线性基
大概只是粘板子之类的..
因为比较懒,可能一些所有代码省略了
以下片段
#include<bits/stdc++.h>
using namespace std;
namespace IO{
typedef long long LL;
typedef double DB;
#define int LL
int read(){
int x=0,f=0; char ch=getchar();
while(ch>'9'||ch<'0'){ f|=(ch=='-'); ch=getchar(); }
while(ch>='0'&&ch<='9'){ x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); }
return f?-x:x;
} char output[50];
void write(int x,char sp){
int len=0;
if(x<0) putchar('-'), x=-x;
do{ output[len++]=x%10+'0'; x/=10; }while(x);
for(int i=len-1;~i;i--) putchar(output[i]); putchar(sp);
}
void ckmax(int& x,int y){ x=x>y?x:y; }
void ckmin(int& x,int y){ x=x<y?x:y; }
} using namespace IO;
const int NN=...
卡特兰数
见得比较多的问题是网格路径数和单调栈相关的计数。另外有递推
看到这样的 \(DP\) 递推式可以直接求卡特兰数。如果模数不是质数可以考虑分解质因子计算贡献。(有时需要高精度
例(网格)
namespace BigIntegers{
const int base=1e10;
struct Int{
int len,c[500];
Int(){ len=0; memset(c,0,sizeof(c)); }
Int(int x){ len=1; memset(c,0,sizeof(c)); c[1]=x; }
Int operator+(const Int& a)const{
Int res(0); res.len=max(len,a.len);
for(int i=1;i<=res.len;i++){
res.c[i]+=c[i]+a.c[i];
res.c[i+1]+=res.c[i]/base;
res.c[i]%=base;
}
if(res.c[res.len+1]) ++res.len;
return res;
}
Int operator-(const Int& a)const{
Int res(0); res.len=len;
for(int i=1;i<=len;i++){
res.c[i]+=c[i]-a.c[i];
if(res.c[i]<0) res.c[i]+=base, --res.c[i+1];
}
if(!res.c[res.len]) --res.len;
return res;
}
Int operator*(const int& a)const{
Int res(0); res.len=len;
for(int i=1;i<=len;i++){
res.c[i]+=c[i]*a;
res.c[i+1]+=res.c[i]/base;
res.c[i]%=base;
}
if(res.c[res.len+1]) ++res.len;
return res;
}
};
void write(Int x,char sp){
printf("%lld",x.c[x.len]);
for(int i=x.len-1;i;i--) printf("%010lld",x.c[i]);
putchar(sp);
}
} using namespace BigIntegers;
namespace Combinatorics{
int cnt,pri[NN],low[NN],tim[NN];
int qpow(int a,int b,int res=1){
for(;b;b>>=1,a*=a)
if(b&1) res*=a;
return res;
}
void prprprprprprprprpr(){
for(int i=2;i<=10000;i++){
if(!low[i]) pri[++cnt]=i, low[i]=i;
for(int j=1;j<=cnt&&i*pri[j]<=10000;j++){
low[i*pri[j]]=pri[j];
if(i%pri[j]==0) break;
}
}
}
Int Cat(int n,int m){
Int res(1);
memset(tim,0,sizeof(tim));
for(int i=n-m+1;i<=n;i++) ++tim[i];
for(int i=1;i<=m;i++) --tim[i];
for(int i=n;i>1;i--) if(low[i]!=i){
tim[low[i]]+=tim[i];
tim[i/low[i]]+=tim[i];
}
for(int i=2;i<=n;i++) if(low[i]==i)
res=res*qpow(i,tim[i]);
return res;
}
} using namespace Combinatorics;
prufer序列
其实构造之类的并不会
但应该也不会考构造这玩意,主要就是用于树的计数。
一颗 \(n\) 个节点的无向树对应的 \(prufer\) 序列长度为 \(n-2\) ,每个点在序列中出现次数为它的度数 \(-1\) 。
一个 \(prufer\) 序列与一棵树构成双射,因此可以直接用可重集排列解决带度数限制的树的计数问题。
其他注意事项同卡特兰数。
BSGS
北上广深变身怪兽拔山盖世算法?
用来解决离散对数问题。形如 \(a^x\equiv b(\mod p)\) 的式子,求解 \(x\) 。
令 \(x=A\left\lceil\sqrt{p}\right\rceil-B\) ,其中 \(A,B\leq\left\lceil\sqrt{p}\right\rceil\) ,不难发现这样可以表示模 \(p\) 意义下的所有 \(x\) 。
那么有
先枚举 \(B\) 算出同余式右的所有取值,然后枚举 \(A\) ,检查是否有合法解即可。最后答案为 \(A\left\lceil\sqrt{p}\right\rceil-B\) 。
并不会扩展BSGS
模板
unordered_map<int,int>has;
int qpow(int a,int b,int res=1){
for(;b;b>>=1,a=a*a%p)
if(b&1) res=res*a%p;
return res;
}
int bsgs(int a,int b,int p){
int t=sqrt(p)+1;
for(int i=0;i<=t;i++)
has[b*qpow(a,i)%p]=i;
a=qpow(a,t);
if(!a) return b?-1:1;
for(int i=1;i<=t;i++){
int tmp=qpow(a,i),pos;
pos=has.count(tmp)?has[tmp]:-1;
if(pos>=0&&i*t-pos>=0) return i*t-pos;
}
return -1;
}
杜教筛
其实这玩意没啥模板。。
狄利克雷卷积,令 \(h(n)=f(n)*g(n)\) ,那么有
若 \(f(n)\) , \(g(n)\) 都为积性函数,则 \(h(n)\) 为积性函数。
计算积性函数前缀和,设 \(f(n)\) 为此函数, \(s(n)=\sum_{i=1}^nf(n)\) , \(g(n)\) 为另一数论函数。则有
因此
一般选用的 \(g\) 第一项都为 \(1\) ,所以可以据此式递归得到 \(s(n)\) 。
常见积性函数:
- \(\mu(n)=\begin{cases}0 &若n=1 \\ (-1)^k &若n无平方数因子,k为n的质因子个数 \\ 1 &若n有平方数因子 \end{cases}\)
- \(\varphi(n)=n\prod_{p\in\mathbb{P}\wedge p|n}\frac{p-1}{p}\) ,为小于 \(n\) 且与 \(n\) 互质的正整数个数
- \(d_x(n)=\sum_{i|n}i^x\)
- \(I(n)=1\)
- \(id(n)=n\)
- \(\epsilon(n)=[n=1]\)
常见卷积:
- \(\mu(n)*I(n)=\epsilon(n)\)
- \(\varphi(n)*I(n)=id(n)\)
剩下就自己试试吧。。可用的 \(g\) 也就那么几个 \(I,id,\epsilon\) 。
好像推导没用啥积性函数性质。但积性函数才能筛前缀和预处理。
莫比乌斯函数与欧拉函数
namespace Math_Theory{
const int UP=2000000;
int cnt,muu[NN],pri[NN],phi[NN];
unordered_map<int,int>smu,sph;
bool vis[NN];
void get_functions(){
muu[1]=phi[1]=1;
for(int i=2;i<=UP;i++){
if(!vis[i]) pri[++cnt]=i, muu[i]=-1, phi[i]=i-1;
for(int j=1;j<=cnt&&i*pri[j]<=UP;j++){
int now=i*pri[j];
vis[now]=1;
if(i%pri[j]==0){
muu[now]=0; phi[now]=phi[i]*pri[j];
break;
}
muu[now]=-muu[i]; phi[now]=phi[i]*(pri[j]-1);
}
muu[i]+=muu[i-1]; phi[i]+=phi[i-1];
}
}
int Smu(int x){
if(x<=UP) return muu[x];
if(smu.count(x)) return smu[x];
int res=1;
for(int l=2,r;l<=x;l=r+1){
r=x/(x/l);
res-=(r-l+1)*Smu(x/l);
}
return smu[x]=res;
}
int Sph(int x){
if(x<=UP) return phi[x];
if(sph.count(x)) return sph[x];
int res=x*(x+1)/2;
for(int l=2,r;l<=x;l=r+1){
r=x/(x/l);
res-=(r-l+1)*Sph(x/l);
}
return sph[x]=res;
}
} using namespace Math_Theory;
Burnside引理&&Polya定理
用来解决等价类计数问题。
定义 \(A\) 和 \(B\) 为有限集合, \(X\) 为一些 \(A\) 到 \(B\) 的映射构成的集合, \(G\) 为 \(A\) 上的置换群, \(\frac{X}{G}\) 为 \(X\) 在 \(G\) 作用下产生的等价类集合, \(X^g\) 为 \(X\) 中在置换 \(g\) 作用下不变的元素构成的集合。
(没有一句人话粘 \(OI-wiki\) 的例子吧。。
Burnside引理
证明不会。
Polya定理
如果 \(X\) 为所有映射构成的集合,则有
其中 \(c\) 为置换 \(g\) 能拆分成的不相交循环置换的数量。
一般循环移位移 \(i\) 位时,设总长为 \(n\) ,则 \(c(g)=\operatorname{gcd}(i,n)\) 。
没有板子。题好难。
线性基
一个数组 \(A\) 的线性基 \(B\) 满足 \(A\) 子集异或和构成的集合与 \(B\) 子集异或和构成的集合相同,且 \(B\) 在此基础上大小最小。
可以证明 \(|B|\) 是 \(\log(值域)\) 的。构造方法见代码。
可用于求解异或和集合大小,最大子集异或和,一个数能否被异或出等问题。
不算 \(0\) 的话异或和集合大小为 \(2^{|B|}-1\) ,有 \(0\) 另算。
求最大子集异或和直接从高位到低位贪心异或;求一个数能否被异或出也类似,如果最后能异或为 \(0\) 则可以。
合并线性基直接暴力合并。
板子简单。题好难。
模板
namespace LinearBasis{
const int BB=64;
struct basis{
int tot,p[BB];
basis(){ memset(p,0,sizeof(p)); }
void insert(int num){
for(int i=63;~i;i--)
if(num&(1ll<<i)){
if(!p[i]){ ++tot; p[i]=num; break; }
num^=p[i];
}
}
int mx(int res=0){
for(int i=63;~i;i--)
ckmax(res,res^p[i]);
return res;
}
bool check(int num){
for(int i=63;~i;i--)
if(num&(1ll<<i)) num^=p[i];
return !num;
}
};
basis merge(basis x,basis y){
if(x.tot<y.tot) swap(x,y);
for(int i=63;~i;i--)
if(y.p[i]) x.insert(y.p[i]);
return x;
}
} using namespace LinearBasis;
拉格朗日插值
已知 \(n+1\) 个点值,求对应 \(n\) 次多项式。
求一个多项式的次数一般用差分法,如果这个多项式差分后为 \(n\) 次多项式,则它为 \(n+1\) 次多项式。
O(n^2)求单点值
int n,k,x[NN],y[NN];
int qpow(int a,int b,int res=1){
for(;b;b>>=1,a=a*a%mod)
if(b&1) res=res*a%mod;
return res;
}
int inter(int *nx,int *ny,int ord,int x){
int res=0; ++ord;
for(int i=1;i<=ord;i++){
int up=ny[i],dn=1;
for(int j=1;j<=ord;j++) if(i^j)
(up*=(x-nx[j]+mod))%=mod, (dn*=(nx[i]-nx[j]+mod))%=mod;
res+=up*qpow(dn,mod-2)%mod;
}
return res%mod;
}
O(n^3)求系数
namespace Mathematics{
int len,x[NN],y[NN],a[NN],f[NN];
int qpow(int a,int b=mod-2,int res=1){
for(;b;b>>=1,a=a*a%mod)
if(b&1) res=res*a%mod;
return res;
}
void poly_mul(int x,int y){
++len;
for(int i=len;i;i--)
a[i]=(a[i]*y+a[i-1]*x)%mod;
a[0]=a[0]*y%mod;
}
void poly_pls(){
for(int i=0;i<=len;i++)
f[i]=(f[i]+a[i])%mod, a[i]=0;
len=0; a[0]=1;
}
void numb_mul(int x){
for(int i=0;i<=len;i++)
a[i]=a[i]*x%mod;
}
void poly_calc(int *x,int *y,int ord,int *f){
a[0]=1; ++ord;
for(int mul,i=1;i<=ord;i++){
mul=qpow(y[i]);
for(int j=1;j<=ord;j++) if(i^j){
poly_mul(1,mod-x[j]);
mul=mul*(mod+x[i]-x[j])%mod;
}
numb_mul(qpow(mul)); poly_pls();
}
}
} using namespace Mathematics;
值域连续时O(n)求单点(连续数幂和)
int qpow(int a,int b=mod-2,int res=1){
for(;b;b>>=1,a=a*a%mod)
if(b&1) res=res*a%mod;
return res;
}
void init(int k){ for(int i=1;i<=k+1;i++) y[i]=(y[i-1]+qpow(i,k))%mod; }
int inter(int ord,int x){
if(x<=ord) return y[x];
int res=0,typ=(ord&1)?-1:1,tmp=1;
for(int i=1;i<=ord;i++) tmp=tmp*(x-i)%mod;
for(int i=1;i<=ord;i++) tmp=tmp*qpow(i)%mod;
for(int i=0;i<=ord;i++,typ=-typ){
res=(res+typ*y[i]*tmp%mod)%mod;
tmp=tmp*(x-i)%mod*qpow(x-i-1)%mod;
tmp=tmp*(ord-i)%mod*qpow(i+1)%mod;
}
return (res+mod)%mod;
}
原根
由欧拉定理,当 \(a\in\mathbb{Z},m\in\mathbb{N_+},\gcd(a,m)=1\) 时, \(a^{\varphi(m)}\equiv 1(\mod m)\) 。因此存在满足 \(a^n\equiv 1(\mod m)\) 的最小正整数 \(n\) ,称 \(n\) 为 \(a\) 模 \(m\) 的阶,记作 \(\delta_m(a)=n\) 。
一个正整数 \(m\) 的原根 \(g\) ,满足 \(\gcd(g,m)=1\) ,且 \(\delta_m(g)=\varphi(m)\) 。若 \(m\) 为质数,则 \(g^x,x\in [1,m)\) 能取到 \([1,m)\) 区间内所有整数。
一个数 \(m\) 有原根,当且仅当 \(m\) 为 \(2,4\) 或奇素数的整数幂。
素数判定定理:设 \(m\geq 3\) ,由唯一分解定理将 \(\varphi(m)\) 分解为 \(\prod p_i^{a_i}\) ,则 \(n\) 是 \(m\) 的原根,当且仅当对所有 \(p_i\) ,有 \(n^{\frac{\varphi(m)}{p_i}}\not\equiv 1(\mod m)\) 。
若 \(m\) 有原根,则它的最小原根 \(n\) 是 \(O(\sqrt[4]m)\) 的。因此直接枚举即可。
目前见到的套路是与BSGS结合,把类似离散对数,但是求解底数的题转化到指数上。
找素数原根
vector<int>fac;
int qpow(int a,int b,int res=1){
for(;b;b>>=1,a=a*a%mod)
if(b&1) res=res*a%mod;
return res;
}
void fact(int x){
int tmp=x;
for(int i=2;i*i<=x;i++) if(tmp%i==0){
fac.push_back(i);
while(tmp%i==0) tmp/=i;
}
if(tmp^i) fac.push_back(tmp);
}
int get_root(int p){
fact(p-1);
for(int i=2;i<=p;i++){
for(int v:fac)
if(qpow(i,(p-1)/v)==1) goto skip;
return i; skip:;
}
return -1;
}