计数/数论相关+插值与线性基

大概只是粘板子之类的..

因为比较懒,可能一些所有代码省略了

以下片段
#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=...

卡特兰数

\[Cat_i=\frac{\binom{2i}{i}}{i+1} \]

见得比较多的问题是网格路径数和单调栈相关的计数。另外有递推

\[Cat_n=\begin{cases}\sum_{i=1}^nCat_{i-1}Cat_{n-i} & n\geq 2,n\in\mathbb{N_+}\\1 & n=0,1\end{cases} \]

看到这样的 \(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\)

那么有

\[a^{A\left\lceil\sqrt{p}\right\rceil-B}\equiv b(\mod p) \]

\[a^{A\left\lceil\sqrt{p}\right\rceil}\equiv a^Bb(\mod p) \]

先枚举 \(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)\) ,那么有

\[h(n)=\sum_{d|n}f(d)g(\frac{n}{d}) \]

\(f(n)\)\(g(n)\) 都为积性函数,则 \(h(n)\) 为积性函数。

计算积性函数前缀和,设 \(f(n)\) 为此函数, \(s(n)=\sum_{i=1}^nf(n)\)\(g(n)\) 为另一数论函数。则有

\[\begin{align*} \sum_{i=1}^nh(i) &=\sum_{i=1}^n\sum_{d|i}f(d)g(\frac{i}{d})\\ &=\sum_{i=1}^n\sum_{j=1}^{\left\lfloor\frac{n}{i}\right\rfloor}g(i)f(j)\\ &=\sum_{i=1}^ng(i)s(\left\lfloor\frac{n}{i}\right\rfloor) \end{align*} \]

因此

\[\sum_{i=1}^nh(i)=\sum_{i=1}^ng(i)s(\left\lfloor\frac{n}{i}\right\rfloor)\Rightarrow g(1)s(n)=\sum_{i=1}^nh(i)-\sum_{i=2}^ng(i)s(\left\lfloor\frac{n}{i}\right\rfloor) \]

一般选用的 \(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\) 的例子吧。。
image

Burnside引理

\[|\frac{X}{G}|=\frac{1}{|G|}\sum_{g\in G}|X^g| \]

证明不会。

Polya定理

如果 \(X\) 为所有映射构成的集合,则有

\[|\frac{X}{G}|=\frac{1}{|G|}\sum_{g\in G}|B|^{c(g)} \]

其中 \(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\) 次多项式。

\[f(x)=\sum_{i=1}^{n+1} y_i\prod_{j\neq i}\frac{x-x_j}{x_i-x_j} \]

求一个多项式的次数一般用差分法,如果这个多项式差分后为 \(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;
}
posted @ 2021-12-30 21:41  keen_z  阅读(61)  评论(0编辑  收藏  举报