ACM模板

目录

  • 语法

    • c++
    • java
  • 动态规划
    • 多重背包
    • 最长不下降子序列LIS
  • 计算几何
    • 向量(结构体)
    • 判断两条线段是否相交
    • 曼哈顿距离、切比雪夫距离
    • Pick定理
    • 二维凸包
    • 点是否在线段上
    • 多边形面积
    • 多边形的面积重心
    • 三维向量(结构体)
    • 三维凸包
  • 数据结构
    • ST表
    • 单调队列MQ
    • 树状数组BIT
    • 超级树状数组SPBIT
    • 线段树ST
    • 指针版线段树ST
    • 带内存池指针版线段树ST
    • 并查集DSU
    • 左偏树LLT
    • zkw线段树
    • 多关键字堆
    • 双头优先队列
  • 数学
    • 唯一分解
    • 线性递推二项式系数
    • 单个欧拉函数
    • 编码与解码
    • 素数判定
    • 大数分解rho
    • 反素数问题
    • 博弈论
    • 拉格朗日插值
    • 快速傅里叶变换FFT
    • 筛素数
    • 筛欧拉函数
    • 筛莫比乌斯函数
    • 线性递推乘法逆元
    • 离线乘法逆元
    • 矩阵乘法、矩阵快速幂
    • 中国剩余定理CRT,及其扩展
    • 大步小步BSGS,及其拓展
    • 莫比乌斯反演
  • 图论
    • 图论的一些概念
    • Havel-Hakimi定理
    • 最短路径,最小环
    • 最小生成树Kruskal算法
    • 树的直径(最长路径)
    • 树的重心(以重心为根,其最大儿子子树最小)
    • 最近公共祖先LCA
    • 强联通分量Tarjan
    • 拓扑排序
    • 欧拉路径/回路
    • 二分图匹配
  • 小技巧
    • 最大公约数
    • 扩展欧几里得
    • 扩欧版乘法逆元
    • 快速幂
    • 快速幂版乘法逆元
    • 唯一分解
    • 位运算巨佬操作
    • 离散化
    • 遍历二进制数表示的集合的非空子集
    • 线性递推组合数
    • 双关键字比较
    • 模乘法
    • 质数表
  • 杂项
    • 快读快写
    • 主定理
    • 01分数规划
    • 归并排序,求逆序数
    • 高维前缀和
    • STL手写hash
    • 模拟退火SA
    • 对拍
  • 字符串
    • KMP算法,前缀数组pi[]
    • Z函数,扩展kmp
    • 字典树
    • 马拉车

语法

c++

#include <bits/stdc++.h>
using namespace std;
using ll=long long; //using lll=__int128;
using pii=pair<int,int>;
#define repeat(i,a,b) for(int i=(a),iE=(b);i<iE;i++)
#define repeat_back(i,a,b) for(int i=(b)-1,iE=(a);i>=iE;i--)
#define mst(a,x) memset((a),(x),sizeof(a))
#define qwq {cout<<"qwq"<<endl;}
const int N=100010;
const int inf=1e9;
const int mod=1000000007;
//#define int ll
signed main(){
    ios_base::sync_with_stdio(0); cin.tie(0); cout.tie(0);
    return 0;
}

平板电视红黑树

#include <ext/pb_ds/tree_policy.hpp>
#include <ext/pb_ds/assoc_container.hpp>
using namespace __gnu_pbds;
tree<pii,null_type,less<pii>,rb_tree_tag,tree_order_statistics_node_update> t;//红黑树
t.insert({x,i+1});//----------------- 插入x,用独特的正整数i+1标注(因为erase太辣鸡)
t.erase(t.lower_bound({x,0}));//----- 删除x(删除单个元素)
t.order_of_key({x,0})+1;//----------- x的排名(小于x的元素个数+1)
t.find_by_order(x-1)->first;//------- 排名为x的元素(第x小的数)
prev(t.lower_bound({x,0}))->first;//- x的前驱(小于x且最大)
t.lower_bound({x+1,0})->first;//----- x的后继(大于x且最小)

随机数mt19937
高级一点的随机数mt19937(是一个类),比rand()快
随机数范围unsigned int

#include<random>
mt19937 rnd(time(0));
cout<<rnd()<<endl;

bitset

bitset<32> b; //声明一个32位的bitset
b[n]; b[n]=1; //访问和修改
b.reset(); b.set(); //全部置0或置1
b.none(); //返回是否为空
b.count(); //返回1的个数
b.flip(); b.flip(n); //全反、反转第n位
支持所有位运算

complex

complex<double> c; //复数
complex<double> c(1,2);
c.real(); //实部
c.imag(); //虚部

浮点数

float 1e38, 有效数字6
double 1e308, 有效数字15
long double 1e4932, 有效数字18

STL函数

将第k大的数置于k这个位置: nth_element(begin,begin+k,end);
第一个大于num的数字地址: upper_bound(begin,end,num)
第一个小于num的数字地址: upper_bound(begin,end,num,greater<type>())
第一个大于等于num的数字地址: lower_bound(begin,end,num)
第一个小于等于num的数字地址: lower_bound(begin,end,num,greater<type>())
是否存在: binary_search(begin,end,num,[less/greater])

decltype

int n; decltype(n) n2; //decltype(n) 等价于 int

手动开启Ofast

#pragma GCC optimize("Ofast")

ifdef

#define DEBUG

#ifdef DEBUG
    // do something
#endif
#ifndef DEBUG
    // do something
#endif

进制转换

itoa(n,s,base); //将 n 转换到 base 进制存入字符数组s中
atoi(s); //返回字符数组 s 的代表的十进制整数

cmath

lround(x),lrint(x)//四舍五入到long
llround(x),llrint(x);//四舍五入到long long

java

import java.util.*;
import java.math.BigDecimal;
import java.math.BigInteger;
public class Main{
static Scanner sc;
public static void main(String[] args){
    sc=new Scanner(System.in);
}
}

编译运行 java Main.java
编译 javac Main.java //生成Main.class
运行 java Main

数据类型

int     //4字节有符号
long    //8字节有符号
double
boolean
char    //还能表示Unicode字符
String  //是个引用

final double PI=3.14; //final => const
var n=1; //var => auto

long型常量结尾加L,如1L

数组

int[] arr=new int[100];//数组
int[] arr=new int[]{1,2,3};//有初始值、不指定长度的声明
int[] arr={1,2,3};//有初始值、不指定长度的声明
arr.length//获取数组长度
int[][] arr=new int[10][10];//二维数组

输出

System.out.print(x);
System.out.println();
System.out.println(x);//自动换行
System.out.printf("%.2f\n",d);//格式化:%d %f %s

输入

import java.util.Scanner;
Scanner sc=new Scanner(System.in);//初始化
String s=sc.nextline();//读一行字符串
int n=sc.nextInt();//读整数
double d=sc.nextDouble();//读实数

if,switch,while,do while,for,for(i:A)同C++

Arrays

import java.util.Arrays;
Arrays.sort(arr);//对int[]排序

String

不可变性:内部是常量
比较:s1.equals(s2)
是否包含子串s2:s1.contains(s2)
查找子串:s1.indexOf(s2)
反向查找子串:s1.lastIndexOf(s2)
获取子串:s1.substring(2,4)//首末坐标[2,4)
提取字符:s1.charAt(3)//就像c++的s1[3]
s1.equalsIgnoreCase(s2)
s1.startsWith(s2)//boolean
s1.endsWith(s2)//boolean

Math

//不用import就能用下列函数
Math.{sqrt,sin,atan,abs,max,min,pow,exp,log,PI,E}

Random

import java.util.Random;
Random rnd=new Random();//已经把时间戳作为了种子
rnd.nextInt();
rnd.nextInt(n);//[0,n)

BigInteger

import java.math.BigInteger;
BigInteger n=new BigInteger("0");
n.intValue()//转换为int
n.longValue()//转换
n.doubleValue()//转换
n.add(n2)//加法
n.subtract(n2)//减法
n.multiply(n2)//乘法
n.divide(n2)//除法
n.mod(n2)//取模
BigInteger.valueOf(I)//int转换为BigInteger

BigDecimal

import java.math.BigDecimal;
n.divide(n2,2,BigDecimal.ROUND_HALF_UP)//保留两位(四舍五入)

动态规划

多重背包

//二进制优化版,m是总容量
repeat(i,0,n)
for(int b=1;num[i]>0;num[i]-=b,b<<=1){
    int cost=min(b,num[i])*w[i];
    repeat(j,cost,m+1)
        dp[j]=max(dp[j],dp[j-cost]+val[i]);
}
return dp[m];
//单调队列优化版
//空缺

最长不下降子序列LIS

const int inf=1e9;
repeat(i,0,n+1)dp[i]=inf;//初始化为inf
repeat(i,0,n)
    *lower_bound(dp,dp+n,a[i])=a[i];
return lower_bound(dp,dp+n,inf)-dp;

计算几何

误差分析
const double err=1e-7;

向量(结构体)

struct vec{
    double x,y;
    vec(double x=0,double y=0):x(x),y(y){};
    vec operator-(vec b){return vec(x-b.x,y-b.y);}
    vec operator+(vec b){return vec(x+b.x,y+b.y);}
    vec operator*(double k){return vec(k*x,k*y);}
    bool operator<(vec b)const{return make_pair(x,y)<make_pair(b.x,b.y);}
    double len(){return sqrt(x*x+y*y);}
}a[N];
double cross(vec a,vec b){return a.x*b.y-a.y*b.x;}
double dot(vec a,vec b){return a.x*b.x+a.y*b.y;}

判断两条线段是否相交

//快速排斥实验:判断线段所在矩形是否相交(减小常数,可省略)
//跨立实验:任一线段的两端点在另一线段的两侧
bool judge(vec a,vec b,vec c,vec d){ //线段ab和线段cd
    #define SJ(x) max(a.x,b.x)<min(c.x,d.x)    || max(c.x,d.x)<min(a.x,b.x)
    if(SJ(x) || SJ(y))return false;
    #define SJ2(a,b,c,d) cross(a-b,a-c)*cross(a-b,a-d)<=0
    return SJ2(a,b,c,d) && SJ2(c,d,a,b);
}

曼哈顿距离、切比雪夫距离

曼:mdist=|x1-x2|+|y1-y2|
切:cdist=max(|x1-x2|,|y1-y2|)
转换:
mdist((x,y),*)=cdist((x+y,x-y),**)
cdist((x,y),*)=mdist(((x+y)/2,(x-y)/2),**)

Pick定理

正方形点阵:面积=内部点数+边上点数/2-1
三角形点阵:面积=2*内部点数+边上点数-2

二维凸包

上凸包:按x坐标(键二为y)升序排序,从小到大加入表U,如果出现凹多边形情况则删除倒数第二个点
下凸包:反着来

struct vec{
    double x,y;
    vec(double x=0,double y=0):x(x),y(y){};
    vec operator-(vec b){return vec(x-b.x,y-b.y);}
    bool operator<(vec b)const{return make_pair(x,y)<make_pair(b.x,b.y);}
}a[N];
double cross(vec a,vec b){return a.x*b.y-a.y*b.x;}
#define judge(a,b,c) cross(a-b,b-c)>0
//这个judge会得到顺时针的凸包
vector<vec> st;
void push(vec v){
    while(st.size()>=2
    && judge(*++st.rbegin(),st.back(),v))
        st.pop_back();
    st.push_back(v);
}
void convex(vec a[],int n){
    st.clear();
    sort(a,a+n);
    repeat(i,0,n)push(a[i]);
    repeat_back(i,0,n-1)push(a[i]);
    //repeat_back自动变成下凸包
}

点是否在线段上

bool onseg(vec a,vec b,vec p){
    return (a.x-p.x)*(b.x-p.x)<err
    && (a.y-p.y)*(b.y-p.y)<err
    && fabs(cross(a-b,a-p))<err;
}

多边形面积

double area(vec a[],int n){
    double ans=0;
    repeat(i,0,n)
        ans+=cross(a[i],a[(i+1)%n]);
    return fabs(ans/2);
}

多边形的面积重心

vec centre(vec a[],int n){
    double S=0;
    vec v=vec();
    repeat(i,0,n){
        vec &v1=a[i],&v2=a[(i+1)%n];
        double s=cross(v1,v2);
        S+=s;
        v=v+(v1+v2)*s;
    }
    return v*(1/(3*S));
}

三维向量(结构体)

using lf=double;
struct vec{
    lf x,y,z;
    vec(lf x=0,lf y=0):x(x),y(y){};
    vec operator-(vec b){return vec(x-b.x,y-b.y,z-b.z);}
    vec operator+(vec b){return vec(x+b.x,y+b.y,z+b.z);}
    vec operator*(lf k){return vec(k*x,k*y,k*z);}
    bool operator<(vec b)const{return make_tuple(x,y,z)<make_pair(b.x,b.y,b.z);}
    lf len(){return sqrt(x*x+y*y+z*z);}
}a[N];
vec cross(vec a,vec b){
    return vec(
    a.y*b.z-a.z*b.y,
    a.z*b.x-a.x*b.z,
    a.x*b.y-a.y*b.x);
}
lf dot(vec a,vec b){return a.x*b.x+a.y*b.y+a.z*b.z;}

三维凸包

将所有凸包上的面放入面集f中,其中face::p[i]作为a的下标

const double err=1e-9;
using lf=double;
struct vec{
    lf x,y,z;
    vec(lf x=0,lf y=0,lf z=0):x(x),y(y),z(z){};
    vec operator-(vec b){return vec(x-b.x,y-b.y,z-b.z);}
    lf len(){return sqrt(x*x+y*y+z*z);}
    void shake(){//微小扰动
        x+=(rand()*1.0/RAND_MAX-0.5)*err;
        y+=(rand()*1.0/RAND_MAX-0.5)*err;
        z+=(rand()*1.0/RAND_MAX-0.5)*err;
    }
}a[N];
vec cross(vec a,vec b){
    return vec(
    a.y*b.z-a.z*b.y,
    a.z*b.x-a.x*b.z,
    a.x*b.y-a.y*b.x);
}
lf dot(vec a,vec b){return a.x*b.x+a.y*b.y+a.z*b.z;}
struct face{
    int p[3];
    vec normal(){//法向量
        return cross(a[p[1]]-a[p[0]],a[p[2]]-a[p[0]]);
    }
    double area(){return normal().len()/2.0;}
};
vector<face> f;
bool see(face f,vec v){
    return dot(v-a[f.p[0]],f.normal())>0;
}
void convex(vec a[],int n){
    static vector<face> c;
    static bool vis[N][N];
    repeat(i,0,n)a[i].shake();//防止四点共面
    f.clear();
    f.push_back((face){0,1,2});
    f.push_back((face){0,2,1});
    repeat(i,3,n){
        c.clear();
        repeat(j,0,f.size()){
            bool t=see(f[j],a[i]);
            if(!t)//加入背面
                c.push_back(f[j]);
            repeat(k,0,3){
                int x=f[j].p[k],y=f[j].p[(k+1)%3];
                vis[x][y]=t;
            }
        }
        repeat(j,0,f.size())
        repeat(k,0,3){
            int x=f[j].p[k],y=f[j].p[(k+1)%3];
            if(vis[x][y] && !vis[y][x])//加入新面
                c.push_back((face){x,y,i});
        }
        f.swap(c);
    }
}

数据结构

ST表

利用倍增实现对区间的查询操作

//RMQ=区间最值查询问题
struct RMQ{//注意logN=log(N)+2
    #define logN 18
    int f[N][logN],log[N];
    RMQ(){
        log[1]=0;
        repeat(i,2,N)
            log[i]=log[i/2]+1;
    }
    void build(){
        repeat(i,0,n)
            f[i][0]=a[i];
        repeat(k,1,logN)
        repeat(i,0,n-(1<<k)+1)
            f[i][k]=max(f[i][k-1],f[i+(1<<(k-1))][k-1]);//最大值
    }
    int query(int l,int r){
        int s=log[r-l+1];
        return max(f[l][s],f[r-(1<<s)+1][s]);//最大值
    }
}rmq;

单调队列MQ

求所有长度为k的区间中的最值

struct MQ{//查询就用mq.q.front().first
    deque<pii> q;//first:保存的最小值; second:时间戳
    int T;
    void init(){
        q.clear();
        T=0;
    }
    void push(int n){
        T++;
        while(!q.empty() && q.back().first>=n)//min
            q.pop_back();
        q.push_back((pii){n,T});
        while(!q.empty() && q.front().second<=T-k)
            q.pop_front();
    }
}mq;

树状数组BIT

单点操作,询问区间,或区间操作,询问单点

#define lb(x) (x&(-x))
struct BIT{
    ll t[500010];//一倍内存吧
    void init(){
        mst(t,0);
    }
    void add(ll x,ll k){//位置x加上k
        //x++;
        for(;x<=n;x+=lb(x))
            t[x]+=k;
    }
    ll sum(ll x){//求[1,x]的和//[0,x]
        //x++;
        ll ans=0;
        for(;x!=0;x-=lb(x))
            ans+=t[x];
        return ans;
    }
};

超级树状数组SPBIT

基本只允许加法,区间操作和询问

struct SPBIT{
    BIT a,a1;
    void init(){a.init();a1.init();}
    void add(ll x,ll y,ll k){
        a.add(x,k);
        a.add(y+1,-k);
        a1.add(x,k*(x-1));
        a1.add(y+1,-k*y);
    }
    ll sum(ll x,ll y){
        return y*a.sum(y)-(x-1)*a.sum(x-1)-(a1.sum(y)-a1.sum(x-1));
    }
};

线段树ST

区间修改、区间查询

#define zero 0
struct ST{//build(1,1,n) update(1,1,n,?,?,?) query(1,1,n,?,?)
    ll a[400010],lazy[400010];//四倍空间
    void build(int n,int l,int r){
        int lc=n*2,rc=n*2+1,m=(l+r)/2;
        lazy[n]=zero;
        if(l==r){
            a[n]=a0[m];
            return;
        }
        build(lc,l,m);
        build(rc,m+1,r);
        a[n]=a[lc]+a[rc];
    }
    void down(int n,int l,int r){
        int lc=n*2,rc=n*2+1,m=(l+r)/2;
        if(l<r){
            lazy[lc]+=lazy[n];
            lazy[rc]+=lazy[n];
        }
        a[n]+=lazy[n]*(r-l+1);
        lazy[n]=zero;
    }
    void update(int n,int l,int r,int x,int y,ll num){
        int lc=n*2,rc=n*2+1,m=(l+r)/2;
        x=max(x,l); y=min(y,r); if(x>y){down(n,l,r);return;}//!!
        if(x==l && y==r){
            lazy[n]+=num;
            down(n,l,r);
            return;
        }
        down(n,l,r);
        update(lc,l,m,x,y,num);
        update(rc,m+1,r,x,y,num);
        a[n]=a[lc]+a[rc];
    }
    ll query(int n,int l,int r,int x,int y){
        int lc=n*2,rc=n*2+1,m=(l+r)/2;
        x=max(x,l); y=min(y,r); if(x>y)return zero;
        down(n,l,r);
        if(x==l && y==r)
            return a[n];
        return query(lc,l,m,x,y)+query(rc,m+1,r,x,y);
    }
}st;

指针版线段树ST

(l,r,lc,rc更占内存)

#define zero 0
struct ST{
    int data,lazy,l,r;
    ST *lc,*rc;
    ST(int l,int r):l(l),r(r){
        lazy=zero;
        if(l==r){
            data=a[l];
            return;
        }
        int m=(l+r)/2;
        lc=new ST(l,m);
        rc=new ST(m+1,r);
        up();
    }
    void up(){
        data=lc->data+rc->data;
    }
    void down(){
        if(l<r){
            lc->lazy+=lazy;
            rc->lazy+=lazy;
        }
        data+=lazy*(r-l+1);
        lazy=zero;
    }
    void update(int x,int y,int num){
        x=max(x,l); y=min(y,r); if(x>y){down();return;}
        if(x==l && y==r){
            lazy+=num;
            down();
            return;
        }
        down();
        lc->update(x,y,num);
        rc->update(x,y,num);
        up();
    }
    int query(int x,int y){
        x=max(x,l); y=min(y,r); if(x>y)return zero;
        down();
        if(x==l && y==r)
            return data;
        return lc->query(x,y)+rc->query(x,y);
    }
}*st;
//初始化st=new ST(1,n);

带内存池指针版线段树ST

相比上一个,感觉时空没什么优化,但是可以重复构造线段树

#define zero 0
struct ST{
    int data,lazy,l,r;
    ST *lc,*rc;
    ST(int,int);
    ST(){};
    void up(){
        data=lc->data+rc->data;
    }
    void down(){
        if(l<r){
            lc->lazy+=lazy;
            rc->lazy+=lazy;
        }
        data+=lazy*(r-l+1);
        lazy=zero;
    }
    void update(int x,int y,int num){
        x=max(x,l); y=min(y,r); if(x>y){down();return;}
        if(x==l && y==r){
            lazy+=num;
            down();
            return;
        }
        down();
        lc->update(x,y,num);
        rc->update(x,y,num);
        up();
    }
    int query(int x,int y){
        x=max(x,l); y=min(y,r); if(x>y)return zero;
        down();
        if(x==l && y==r)
            return data;
        return lc->query(x,y)+rc->query(x,y);
    }
}*st;
ST pool[200010],*poolt;//两倍内存
ST::ST(int l,int r):l(l),r(r){
    lazy=zero;
    if(l==r){
        data=a[l];
        return;
    }
    int m=(l+r)/2;
    lc=poolt++; *lc=ST(l,m);
    rc=poolt++; *rc=ST(m+1,r);
    up();
}
//初始化poolt=pool; st=poolt++; *st=ST(1,n);

并查集DSU

合并、查找

struct DSU{//合并:d[x]=d[y],查找:d[x]==d[y]
    int a[10010];
    void init(){
        repeat(i,0,n+1)a[i]=i;
    }
    int fa(int x){
        return a[x]==x?x:a[x]=fa(a[x]);
    }
    int& operator[](int x){
        return a[fa(x)];
    }
}d;

左偏树LLT

struct LLT{//编号从1开始,因为空的左右儿子会指向0
    #define lc LC[x]
    #define rc RC[x]
    vector<int> val,dis,exist,dsu,LC,RC;
    void init(){add(0);dis[0]=-1;}
    void add(int v){
        int t=val.size();
        val.pb(v);
        dis.pb(0);
        exist.pb(1);
        dsu.pb(t);
        LC.pb(0);
        RC.pb(0);
    }
    int top(int x){
        return dsu[x]==x?x:dsu[x]=top(dsu[x]);
    }
    void join(int x,int y){
        if(exist[x] && exist[y] && top(x)!=top(y))
            merge(top(x),top(y));
    }
    int merge(int x,int y){
        if(!x || !y)return x+y;
        if(val[x]<val[y])//大根堆
            swap(x,y);
        rc=merge(rc,y);
        if(dis[lc]<dis[rc])
            swap(lc,rc);
        dsu[lc]=dsu[rc]=dsu[x]=x;
        dis[x]=dis[rc]+1;
        return x;
    }
    void pop(int x){
        x=top(x);
        exist[x]=0;
        dsu[lc]=lc;
        dsu[rc]=rc;
        dsu[x]=merge(lc,rc);//指向x的dsu也能正确指向top
    }
    #undef lc
    #undef rc
}llt;
//添加元素llt.add(v),位置是llt.val.size()-1
//是否未被pop:llt.exist(x)
//合并:llt.join(x,y)
//堆顶:llt.val[llt.top(x)]
//弹出:llt.pop(x)

zkw线段树

单点区间

struct zkw{//treelen的值是2^k-1且大于n(从0开始就2^k)
    #define treelen 524287
    ll a[treelen*2+10];//两倍内存
    void init(){
        mst(a,0);
    }
    void up(ll x){
        a[x]=a[x<<1]+a[(x<<1)^1];//+
    }
    void update(ll x, ll k){//位置x加上k
        a[x+=treelen]+=k;//也可以直接赋值
        while(x>>=1)up(x);
    }
    ll sum(ll x){//求[1,x]的和
        ll ans=0;
        for(x+=treelen+1;x^1;x>>=1)
        if(x&1)
            ans=ans+a[x^1];//+
        return ans;
    }
}t;

多关键字堆

自己瞎搞的,查询、弹出指定关键字最大的元素

//pair版
struct HEAP{
    multiset<pii> a[2];
    void init(){a[0].clear();a[1].clear();}
    pii rev(pii x){return {x.second,x.first};}
    void push(pii x){
        a[0].insert(x);
        a[1].insert(rev(x));
    }
    pii top(int p){
        pii t=*--a[p].end();
        return p?rev(t):t;
    }
    void pop(int p){
        auto t=--a[p].end();
        a[p^1].erase(a[p^1].lower_bound(rev(*t)));
        a[p].erase(t);
    }
};
//vector版
using vi=vector<int>
struct HEAP{
    static const int n=2;
    multiset< vi > a[n];
    void init(){
        repeat(i,0,n)
            a[i].clear();
    }
    void push(vi x){
        repeat(i,0,n){
            swap(x[0],x[i]);
            a[i].insert(x);
            swap(x[0],x[i]);
        }
    }
    vi top(int i){
        vi x=*--a[i].end();
        swap(x[0],x[i]);
        return x;
    }
    void pop(int i){
        vi x=top(i);
        repeat(i,0,n){
            swap(x[0],x[i]);
            a[i].erase(a[i].lower_bound(x));
            swap(x[0],x[i]);
        }
    }
};

双头优先队列

自己瞎搞的

struct HEAP{
    multiset<int> s;
    void init(){s.clear();}
    void push(int x){s.insert(x);}
    int front(){return *--s.end();}
    int back(){return *s.begin();}
    void pop_front(){s.erase(--s.end());}
    void pop_back(){s.erase(s.begin());}
};

数学

唯一分解

用数组表示数字唯一分解式的素数的指数,如 50={1,0,2,0,…}
可以用来计算阶乘和乘除操作

线性递推二项式系数

C(n,k)=(n-k+1)*C(n,k-1)/k

单个欧拉函数

\(\varphi(n)=\)小于n且与n互质的正整数个数
n的唯一分解式\(n=Π({p_k}^{a_k})\),则\(\varphi(n)=n\cdot Π(1-1/pk)\)

int euler_phi(int n){
    int m = int(sqrt(n+0.5));
    int ans = n;
    for(int i = 2; i <= m; i++)
    if(n % i == 0){
        ans = ans / i * (i-1);
        while(n % i == 0)n /= i;
    }
    if(n > 1)ans = ans / n * (n-1);
    return ans;
}

编码与解码

(多重集康托展开与逆展开)
<1>
给定一个字符串,求出它的编号
例,输入acab,输出5(aabc,aacb,abac,abca,acab,...)
用递归,令d(S)是小于S的排列数,f(S)是S的全排列数
小于acab的第一个字母只能是a,所以d(acab)=d(cab)
第二个字母是a,b,c,所以d(acab)=f(bc)+f(ac)+d(ab)
d(ab)=0
因此d(acab)=4,加1之后就是答案
<2>
给定编号求字符串,对每一位进行尝试即可

素数判定

bool isprime(int n){//朴素算法
    if(n<4)return n>1;
    if(n%2==0 || n%3==0)return false;
    repeat(i,1,int(sqrt(n)+1.5)/6+1)
        if(n%(i*6-1)==0 || n%(i*6+1)==0)return false;
    return true;
}
bool isprime(int n){//Miller-Rabin素性测试
    if(n<4)return n>1;
    int a=n-1,b=0;
    while(a%2==0)a/=2,++b;
    repeat(i,0,10){
        int x=rand()%(n-2)+2,v=qpow(x,a,n);
        if(v==1 || v==n-1)continue;
        repeat(j,0,b+1){
            v=slow(v,v,n);
            if(v==n-1)break;
        }
        if(v!=n-1)return 0;
    }
    return 1;
}

大数分解rho

//基于MR素性测试
ll pollard_rho(ll c,ll n){
    ll i=1,x,y,k=2,d;
    x=y=rand()%n;
    do{
        i++;
        d=__gcd(n+y-x,n);
        if(d>1 && d<n)
            return d;
        if(i==k)y=x,k*=2;
        x=(slow(x,x,n)+n-c)%n;
    }while(y!=x);
    return n;
}
void rho(ll n){
    if(isprime(n)){
        ans.push_back(n);
        return;
    }
    ll t;
    do{t=pollard_rho(rand()%(n-1)+1,n);}while(t>=n);
    rho(t);
    rho(n/t);
    return;
}

反素数问题

求因数最多的数(因数个数一样则取最小)
抓住两个性质DFS
(之前先判断素数或者打表一下,算出p1...p16的值(253))
质数表:int p[16]={2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53};
\(M = {p_1}^{k_1} * {p_2}^{k_2} * ...\)
其中,\(p_1,p_2,...\)是从2开始的连续质数
DFS时,枚举\(k_n\),可行性剪枝

博弈论

SG定理

有向无环图中,两个玩家轮流推一个棋子,不能走的判负
假设x的k个后继状态{yi}
SG(x)=mex{SG(yi)},mex(S)=不属于集合S的最小自然数
当且仅当XOR{SG(起点1),SG(起点2),...}为0时先手必败
(如果只有一个起点,SG的值可以只考虑01)

例题:拿n堆石子,每次只能拿一堆中的斐波那契数颗石子

void getSG(int n){
    mst(SG,0);
    repeat(i,1,n+1){
        mst(S,0);
        for(int j=0;f[j]<=i && j<=N;j++)
            S[SG[i-f[j]]]=1;
        for(int j=0;;j++)
        if(!S[j]){
            SG[i]=j;
            break;
        }
    }
}

Nim

n堆石子,分别有ai颗石子
两人每次可以拿任意堆任意非空的石子,拿不了的人判负
解:Nim和=XOR{ai},当且仅当Nim和为0时先手必败(SG证)

Nimk

每次最多选取k堆石子,选中的每一堆都取走任意非空的石子
解:当且仅当下述命题成立,先手必胜
存在t使得sum(ai & (1<<t))%(k+1)!=0

拉格朗日插值

//函数曲线通过n个点(xi,yi),求f(k)
//拉格朗日插值:f(x)=sum(i,1,n){yi*PI(j!=i)(x-xj)/(xi-xj)}
repeat(i,0,n)x[i]%=mod,y[i]%=mod;
repeat(i,0,n){
    s1=y[i];
    s2=1;
    repeat(j,0,n)
    if(i!=j){
        s1=s1*(k-x[j])%mod;
        s2=s2*((x[i]-x[j])%mod)%mod;
    }
    ans=(ans+s1*getinv(s2)%mod+mod)%mod;
}

快速傅里叶变换FFT

struct cf{
    double x,y;
    cf(double x=0,double y=0):x(x),y(y){};
    cf operator+(cf b){return cf(x+b.x,y+b.y);}
    cf operator-(cf b){return cf(x-b.x,y-b.y);}
    cf operator*(cf b){return cf(x*b.x-y*b.y,x*b.y+y*b.x);}
};
//或者#define cf complex<double>,输出的x改成real()
const double pi=acos(-1);
void fft(cf *a,int n,int f){//n为2^k,f为正负1,结果存在a
    static int rev[N];
    int bit=0;
    while((1<<bit)<n)bit++;
    repeat(i,0,n){
        rev[i]=(rev[i>>1]>>1)|((i&1)<<(bit-1));
        if(i<rev[i])swap(a[i],a[rev[i]]);
    }
    for(int mid=1;mid<n;mid*=2){
        cf t(cos(pi/mid),f*sin(pi/mid));
        for(int i=0;i<n;i+=mid*2){
            cf om(1,0);
            for(int j=0;j<mid;j++,om=om*t){
                cf x=a[i+j],y=om*a[i+j+mid];
                a[i+j]=x+y,a[i+j+mid]=x-y;
            }
        }
    }
}
cf a[N],b[N];
int c[N];
signed main(){
    ios_base::sync_with_stdio(0); cin.tie(0); cout.tie(0);
    int n=1048576*2,na=read(),nb=read();
    repeat(i,0,na+1)a[i]=cf(read(),0);
    repeat(i,0,nb+1)b[i]=cf(read(),0);
    fft(a,n,1),fft(b,n,1);
    repeat(i,0,n)a[i]=a[i]*b[i];
    fft(a,n,-1);
    repeat(i,0,n)c[i]=llround(a[i].x/n);
    repeat(i,0,na+nb+1)printf("%d ",c[i]);
    return 0;
}

筛素数

//a[i]表示第i+1个质数,vis[i]==0表示i是素数
void get_prime(){
    int cnt=0; vis[1]=1;
    repeat(i,2,n+1){
        if(!vis[i])//是个质数
            a[cnt++]=i;//记录质数
        repeat(j,0,cnt){
            if(i*a[j]>n)break;
            vis[i*a[j]]=1;
            if(i%a[j]==0)break;//此时a[j]是i的最小质因数
        }
    }
}

筛欧拉函数

//线性版
void get_phi(){
    int cnt=0; /*vis[1]=1;*/ phi[1]=1;
    repeat(i,2,n+1){
        if(!vis[i])
            a[cnt++]=i,phi[i]=i-1;
        repeat(j,0,cnt){
            if(i*a[j]>n)break;
            vis[i*a[j]]=1;
            if(i%a[j]==0){
                phi[i*a[j]]=phi[i]*a[j];
                break;
            }
            phi[i*a[j]]=phi[i]*(a[j]-1);
        }
    }
}
//不是线性但节省力气和空间版
void get_phi(){
    phi[1]=1;//其他的值初始化为0
    repeat(i,2,n+1)
    if(!phi[i])
    for(int j=i;j<=n;j+=i){
        if(!phi[j])phi[j]=j;
        phi[j]=phi[j]/i*(i-1);
    }
}

筛莫比乌斯函数

void get_mu(){
    int cnt=0; /*vis[1]=1;*/ mu[1]=1;
    repeat(i,2,n+1){
        if(!vis[i])
            a[cnt++]=i,mu[i]=-1;
        repeat(j,0,cnt){
            if(i*a[j]>n)break;
            vis[i*a[j]]=1;
            if(i%a[j]==0){
                mu[i*p[j]]=0;
                break;
            }
            mu[i*p[j]]=-mu[i];
        }
    }
}

线性递推乘法逆元

void get_inv(){//求1..(n-1)的逆元
    inv[1]=1;
    repeat(i,2,n)
        inv[i]=mod-mod/i*inv[mod%i]%mod;
}

离线乘法逆元

void get_inv(){//求a[1..n]的逆元,存在inv[1..n]中
    static int pre[N]={1};
    repeat(i,1,n+1)
        pre[i]=1ll*pre[i-1]*a[i]%mod;
    int inv_pre=qpow(pre[n],mod-2);
    repeat_back(i,1,n+1){
        inv[i]=1ll*pre[i-1]*inv_pre%mod;
        inv_pre=1ll*inv_pre*a[i]%mod;
    }
}

矩阵乘法、矩阵快速幂

struct matrix{
    ll m[M][M];
    matrix(int e=0){
        mst(m,0);
        repeat(i,0,M)
            m[i][i]=e;
    }
    matrix operator*(matrix &b)const{
        matrix ans;
        repeat(i,0,M)
        repeat(j,0,M){
            ll &t=ans.m[i][j];
            repeat(k,0,M)
                t=(t+m[i][k]*b.m[k][j])%mod;
        }
        return ans;
    }
};
matrix qpow(matrix a,ll b){
    matrix ans(1);
    while(b){
        if(b&1)ans=ans*a;
        a=a*a;
        b>>=1;
    }
    return ans;
}

中国剩余定理CRT,及其扩展

//crt,m[i]两两互质
ll crt(ll a[],ll m[],int n){//ans%m[i]==a[i]
    repeat(i,0,n)a[i]%=m[i];
    ll M=1,ans=0;
    repeat(i,0,n)
        M*=m[i];
    repeat(i,0,n){
        ll k=M/m[i],t=getinv(k%m[i],m[i]);//扩欧!!
        ans=(ans+a[i]*k*t)%M;//两个乘号可能都要slow
    }
    return (ans+M)%M;
}
//excrt,m[i]不必两两互质
ll excrt(ll a[],ll m[],int n){//ans%m[i]==a[i]
    repeat(i,0,n)a[i]%=m[i];
    ll M=m[0],ans=a[0],g,x,y;
    repeat(i,1,n){
        ll c=((a[i]-ans)%m[i]+m[i])%m[i];
        exgcd(M,m[i],g,x,y);//Ax=c(mod B)
        if(c%g)return -1;
        ans+=slow(x,c/g,m[i]/g)*M;//龟速乘
        M*=m[i]/g;
        ans=(ans%M+M)%M;
    }
    return (ans+M)%M;
}

大步小步BSGS,及其拓展

//bsgs,a和mod互质
ll bsgs(ll a,ll b,ll mod){//a^ans%mod==b
    a%=mod,b%=mod;
    static unordered_map<ll,ll> m; m.clear();
    ll t=(ll)sqrt(mod)+1,p=1;
    repeat(i,0,t){
        m[slow(b,p,mod)]=i;//p==a^i
        p=slow(p,a,mod);
    }
    a=p; p=1;
    repeat(i,0,t+1){
        if(m.count(p)){//p==a^i
            ll ans=t*i-m[p];
            if(ans>0)return ans;
        }
        p=slow(p,a,mod);
    }
    return -1;
}
//exbsgs,a和mod不需要互质,以bsgs为基础
ll exbsgs(ll a,ll b,ll mod){//a^ans%mod==b
    a%=mod,b%=mod;
    if(b==1)return 0;
    ll ans=0,c=1,g;
    while((g=__gcd(a,mod))!=1){
        if(b%g!=0)return -1;
        b/=g,mod/=g;
        c=slow(c,a/g,mod);
        ans++;
        if(b==c)return ans;
    }
    ll t=bsgs(a,slow(b,getinv(c,mod),mod),mod);//必须扩欧逆元!!
    if(t==-1)return -1;
    return t+ans;
}

莫比乌斯反演

记\([P]=\left\{\begin{array}{} 1&命题P为真\\0&命题P为假\end{array}\right.\)
引理1:\(\lfloor \frac{a}{bc}\rfloor=\lfloor \frac{\lfloor \frac{a}{b}\rfloor}{c}\rfloor\)
引理2:\(n\) 的因数个数 \(≤\lfloor 2\sqrt n \rfloor\)
数论分块:将 \(\lfloor \frac{n}{x}\rfloor=C\) 的 \([x_{\min},x_{\max}]\) 作为一块,其中区间内的任一整数 \(x_0\) 满足 \(x_{\max}=\lfloor\frac n{\lfloor\frac n x\rfloor}\rfloor\)
def-积性函数:\(\gcd(x,y)=1\Rightarrow f(xy)=f(x)f(y)\)
def-单位函数:\(\varepsilon(n)=[n=1]\)
def-恒等函数:\(id(n)=n\)
def-狄利克雷Dirichlet卷积:\((f*g)(n)=\sum_{d|n}f(d)g(\frac n d)\)
\(\varepsilon\) 为狄利克雷卷积的单位元
莫比乌斯函数性质:\(\mu (n)=\left\{\begin{array}{} 1&n=1\\0&n含有平方因子\\(-1)^k&k为n的本质不同质因数个数\end{array}\right.\)
莫比乌斯函数是积性函数
超级重要结论:\(\varepsilon=\mu*1\),即\(\varepsilon(n)=\sum_{d|n}\mu(d)\)
莫比乌斯反演公式:若\(f=g*1\),则\(g=f*\mu\);或者,若\(f(n)=\sum_{d|n}g(d)\),则\(g(n)=\sum_{d|n}\mu(d)f(\frac n d)\)
(补充)欧拉函数性质:\(\varphi*1=id\)

图论

图论的一些概念

基环图:树加一条边
仙人掌:每条边至多属于一个简单环的无向联通图
简单图:不含重边和自环
多重图:含重边的图
完全图:顶点两两相连的无向图
竞赛图:顶点两两相连的有向图
自环:两个端点是同一顶点的边
简单路径/回路:每个点至多经过一次的路径/回路(有时是每个边)
点u到v可达:有向图中,存在u到v的路径
点u和v联通:无向图中,存在u到v的路径
点割集:极小的,把图分成多个联通块的点集
割点:自身就是点割集的点
边割基:极小的,把图分成多个联通块的边集
桥:自身就是边割集的边
点联通度:最小点割集的大小
边联通度:最小边割集的大小
生成子图:点数和原图相同
导出子图:选取一个点集,尽可能多加边
正则图:所有点的度均相同的无向图
Whitney定理:点联通度≤边联通度≤最小度

Havel-Hakimi定理

给定一个度序列,反向构造出这个图
解:贪心,每次让剩余度(=k)最大的顶点连接其余最大的k个顶点
(我认为二路归并是最快的,可是找到的代码都用sort()

最短路径,最小环

有向图最小环Dijkstra
每条边(u,v),对v进行Dijstra,计算dis[u]+edge[u][v],适用稀图
有向图最小环Floyd
普通Floyd完之后,任意两点,计算dis[i][j]+dis[j][i],适用稠图
无向图最小环:删边,然后Dijkstra

Dijkstra算法

struct node{
    int to,dis;
    bool operator<(node x)const{
        return x.dis<dis;
    }
};
//如果只要求环的大小,可以删掉last数组
//如果(u,v)中v是起点,不断u=last[u]直到u==-1得到环上的顶点
void dijkstra(int s){//s是起点
    repeat(i,0,n){
        vis[i]=0;
        dis[i]=2147483647;
    }
    dis[s]=0; //last[s]=-1;
    q=priority_queue<node>();
    q.push((node){s,0});
    while(!q.empty()){
        x=q.top().to; q.pop();
        if(vis[x])continue; vis[x]=1;
        repeat(i,0,a[x].size()){
            p=a[x][i].to;
            if(dis[p]>dis[x]+a[x][i].dis){//更新
                dis[p]=dis[x]+a[x][i].dis;
                q.push((node){p,dis[p]});
                //last[p]=x;
            }
        }
    }
}
//我觉得vis[x]改成mindis(处理过的最小距离)就能处理负权边了

Floyd算法

repeat(k,0,n)
repeat(i,0,n)
repeat(j,0,n)
    f[i][j]=min(f[i][j],f[i][k]+f[k][j]);

最小生成树Kruskal算法

对边长排序,然后添边,并查集判联通

int Kruskal(){
    int ans=0,cnt=0;
    sort(e,e+m);
    repeat(i,0,m){
        int &x=d[e[i].u],&y=d[e[i].v];
        if(x!=y){
            x=y; ans+=e[i].dis; cnt++;
        }
        if(cnt==n-1)break;
    }
    if(cnt!=n-1)return -1; else return ans;
}

树的直径(最长路径)

以任意一点出发所能达到的最远节点为一个端点
以这个端点出发所能达到的最远节点为另一个端点
两次Dijkstra

树的重心(以重心为根,其最大儿子子树最小)

DFS计算所有子树的size
最后计算最大儿子子树最小的节点
最大儿子子树=max{n-size,max{son[i].size}}

最近公共祖先LCA

欧拉序列RMQ版本(编号从0开始)

int n,m,s;
vector<int> a;
vector<int> e[500010];
bool vis[500010];
int pos[500010],dep[500010];
#define mininarr(a,x,y) (a[x]<a[y]?x:y)
struct RMQ{
    #define logN 21
    int f[N][logN],log[N];
    RMQ(){
        log[1]=0;
        repeat(i,2,N)
            log[i]=log[i/2]+1;
    }
    void build(){
        int n=a.size();
        repeat(i,0,n)
            f[i][0]=a[i];
        repeat(k,1,logN)
        repeat(i,0,n-(1<<k)+1)
            f[i][k]=mininarr(dep,f[i][k-1],f[i+(1<<(k-1))][k-1]);
    }
    int query(int l,int r){
        if(l<r)swap(l,r);//!!
        int s=log[r-l+1];
        return mininarr(dep,f[l][s],f[r-(1<<s)+1][s]);
    }
}rmq;
void dfs(int x,int d){
    if(vis[x])return;
    vis[x]=1;
    dep[x]=d;
    a.push_back(x);
    pos[x]=a.size()-1;//记录位置
    repeat(i,0,e[x].size()){
        int p=e[x][i];
        if(vis[p])continue;
        dfs(p,d+1);
        a.push_back(x);
    }
}
int lca(int x,int y){
    return rmq.query(pos[x],pos[y]);
}

强联通分量Tarjan

vector<int> stk;
bool vis[N],instk[N];
int dfn[N],low[N],belong[N];//belong染色结果
vector<int> sz;//第n个颜色的点数
int dcnt;
void dfs(int x){
    if(vis[x])return;
    vis[x]=1;
    stk.push_back(x);
    instk[x]=1;
    dfn[x]=low[x]=++dcnt;
    for(auto p:a[x]){
        dfs(p);
        if(instk[p])low[x]=min(low[x],low[p]);
    }
    if(low[x]==dfn[x]){
        int t; sz.push_back(0);//记录
        do{
            t=stk.back();
            stk.pop_back();
            instk[t]=0;
            sz.back()++;//记录
            belong[t]=sz.size()-1;//染色
        }while(t!=x);
    }
}

拓扑排序

queue<int> q;
int ideg[N];
vector<int> ans;
void toposort(){
    repeat(x,0,n)
    for(auto p:a[x])
        ideg[p]++;
    repeat(i,0,n)
        if(ideg[i]==0)q.push(i);
    while(!q.empty()){
        int x=q.front(); q.pop();
        ans.push_back(x);
        for(auto p:a[x])
        if(--ideg[p]==0)
            q.push(p);
    }
}

欧拉路径/回路

若存在则路径为DFS退出序
(不记录点的vis,只记录边的vis)

二分图匹配

//空缺

小技巧

最大公约数

__gcd(a,b) //推荐
ll gcd(ll a,ll b){return b?gcd(b,a%b):a;} //不推荐233

扩展欧几里得

求ax+by=gcd(a,b)的一个解,d存gcd(a,b)

void exgcd(ll a,ll b,ll &d,ll &x,ll &y){
    if(!b)d=a,x=1,y=0;
    else exgcd(b,a%b,d,y,x),y-=x*(a/b);
}

扩欧版乘法逆元

ll getinv(ll n,ll mod){
    ll d,x,y;
    exgcd(n,mod,d,x,y);
    return (x%mod+mod)%mod;
}

快速幂

ll qpow(ll a,ll b,ll mod){
    ll ans=1;
    while(b){
        if(b&1)ans=ans*a%mod; //必要时slow
        a=a*a%mod; //必要时slow
        b>>=1;
    }
    return ans;
}

快速幂版乘法逆元

mod必须必须是质数!

ll getinv(ll n,ll mod){
    return qpow(n,mod-2,mod);
}

唯一分解

void fac(int a[],ll n){
    repeat(i,2,(int)sqrt(n)+2)
    while(n%i==0)
        a[i]++,n/=i;
    if(n>1)a[n]++;
}

位运算巨佬操作

中点向下取整(x+y)/2: (x & y) + ((x ^ y) >> 1)
中点向上取整(x+y+1)/2: (x | y) - ((x ^ y) >> 1)
//一般来说用x + (y - x >> 1)
abs(n): (n ^ (n >> 31)) - (n >> 31)
max(a,b): b & ((a - b) >> 31) | a & (~(a - b) >> 31)
min(a,b): a & ((a - b) >> 31) | b & (~(a - b) >> 31)

离散化

(从小到大标号并赋值)

Map.clear();
repeat(i,0,n){
    cin>>a[i];
    Map[a[i]]=0;
}
int cnt=0;
for(auto i:Map)
    Map[i.first]=cnt++;
repeat(i,0,n)a[i]=Map[a[i]];

遍历二进制数表示的集合的非空子集

for(int s=m;s>0;s=(s-1)&m){/*do something*/}

线性递推组合数

O(n)初始化,O(1)查询

int C(int a,int b){//a>=b
    return fac[a]*fac_inv[a-b]%mod*fac_inv[b]%mod;
}
void init(){
    fac[0]=1;
    repeat(i,1,N)fac[i]=fac[i-1]*i%mod;
    fac_inv[N-1]=qpow(fac[N-1],mod-2);
    repeat(i,0,N-1)fac_inv[i]=fac_inv[i+1]*(i+1)%mod;
}

双关键字比较

不如用 make_pair(a.x,a.y)<make_pair(b.x,b.y) 或者 make_tuple
int类型还可以 pii(a.x,a.y)<pii(b.x,b.y)

模乘法

//int128版本
ll slow(ll a,ll b,ll mod){return ll((__int128)x*y%mo);}
//每位运算一次版本
ll slow(ll a,ll b,ll mod){
    ll ans=0;
    while(b){
        if(b&1)ans=(ans+a)%mod;
        a=(a+a)%mod;
        b>>=1;
    }
    return ans;
}
//把b分成两部分版本,要保证mod小于1<<42,约等于4e12,a,b<mod
ll slow(ll a,ll b,ll mod){
    a%=mod,b%=mod;
    ll l=a*(b>>21)%mod*(1ll<<21)%mod;
    ll r=a*(b&(1ll<<21)-1)%mod;
    return (l+r)%mod;
}

质数表

19260817 //某个很好用的质数
1000000007
998244353
下面的 \(g\) 是模 \((r \cdot 2^k+1)\) 的原根

            r*2^k+1   r  k  g
                  3   1  1  2
                  5   1  2  2
                 17   1  4  3
                 97   3  5  5
                193   3  6  5
                257   1  8  3
               7681  15  9 17
              12289   3 12 11
              40961   5 13  3
              65537   1 16  3
             786433   3 18 10
            5767169  11 19  3
            7340033   7 20  3
           23068673  11 21  3
          104857601  25 22  3
          167772161   5 25  3
          469762049   7 26  3
          998244353 119 23  3
         1004535809 479 21  3
         2013265921  15 27 31
         2281701377  17 27  3
         3221225473   3 30  5
        75161927681  35 31  3
        77309411329   9 33  7
       206158430209   3 36 22
      2061584302081  15 37  7
      2748779069441   5 39  3
      6597069766657   3 41  5
     39582418599937   9 42  5
     79164837199873   9 43  5
    263882790666241  15 44  7
   1231453023109121  35 45  3
   1337006139375617  19 46  3
   3799912185593857  27 47  5
   4222124650659841  15 48 19
   7881299347898369   7 50  6
  31525197391593473   7 52  3
 180143985094819841   5 55  6
1945555039024054273  27 56  5
4179340454199820289  29 57  3

杂项

快读快写

ll read(){
    ll x=0,tag=1; char c=getchar();
    for(;!isdigit(c);c=getchar())if(c=='-')tag=-1;
    for(; isdigit(c);c=getchar())x=(x<<3)+(x<<1)+(c^48);
    return x*tag;
}
void write2(ll x) {
    static int a[35];
    int top=0;
    do{a[top++]=x%10;}while(x/=10);
    while(top)putchar(a[--top]+48);
}
void write(ll x){
    if(x<0)x=-x,putchar('-');
    if(x>=10)write(x/10);
    putchar(x%10^48);
}

主定理

对于 T(n)=aT(n/b)+n^k (要估算n^k的k值)
若 log(b,a)>k, 则 T(n)=O(n^log(b,a))
若 log(b,a)=k, 则 T(n)=O(n^k*log(n))
若 log(b,a)<k, (有省略) 则T(n)=O(n^k)

01分数规划

n个物品,都有两个属性a[i]和b[i],任意取m个物品使它们的sum(a[j])/sum(b[j])最大
题解:二分答案
x是否满足条件即判断sum(a[j])/sum(b[j])>=x,即sum(a[j]-x*b[j])>=0
因此对{a[i]-x*b[i]}进行排序,取前k个最大值进行测试

归并排序,求逆序数

void merge(int l,int r){//归并排序
    //对[l,r-1]的数排序
    if(r-l<=1)return;
    int mid=l+(r-l>>1);
    merge(l,mid);
    merge(mid,r);
    int p=l,q=mid,s=l;
    while(s<r){
        if(p>=mid || (q<r && a[p]>a[q])){
            t[s++]=a[q++];
            ans+=mid-p;//统计逆序数
        }
        else
            t[s++]=a[p++];
    }
    for(int i=l;i<r;++i)a[i]=t[i];
}

高维前缀和

(以二维为例)
法一时间复杂度是O(n^t*2^t)(t是维数)
法二是O(n^t*t)

//<1>
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
    b[i][j]=b[i-1][j]+b[i][j-1]-b[i-1][j-1]+a[i][j];
//<2>
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
    a[i][j]+=a[i][j-1];
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
    a[i][j]+=a[i-1][j];

STL手写hash

struct custom_hash {
    static uint64_t splitmix64(uint64_t x) {
        x += 0x9e3779b97f4a7c15;
        x = (x ^ (x >> 30)) * 0xbf58476d1ce4e5b9;
        x = (x ^ (x >> 27)) * 0x94d049bb133111eb;
        return x ^ (x >> 31);
    }
    size_t operator()(uint64_t x) const {
        static const uint64_t FIXED_RANDOM = chrono::steady_clock::now().time_since_epoch().count();
        return splitmix64(x + FIXED_RANDOM);
    }
};
unordered_map<int,int,custom_hash> m;

模拟退火SA

以当前状态为中心,半径为温度的圆内选一个点
计算 d = 当前最优解减去当前解
如果小于 0 则直接状态转移
否则状态转移的概率是 exp(-k*d/t)
最后温度乘以降温系数,返回第一步
需要调 3 个参数:初始温度,终止温度,降温系数(?)

对拍

#include <stdio.h>
#include <stdlib.h>
int main() {
    //For Windows
    //对拍时不开文件输入输出
    //当然,这段程序也可以改写成批处理的形式
    while (1) {
        system("gen > test.in"); //数据生成器将生成数据写入输入文件
        system("test1.exe < test.in > a.out"); //获取程序1输出
        system("test2.exe < test.in > b.out"); //获取程序2输出
        if (system("fc a.out b.out")) {
            //该行语句比对输入输出
            //fc返回0时表示输出一致,否则表示有不同处
            system("pause"); //方便查看不同处
            return 0;
            //该输入数据已经存放在test.in文件中,可以直接利用进行调试
        }
    }
}

字符串

寻找模式p在文本t中的所有出现

KMP算法,前缀数组pi[]

pi[x]表示在前x个字符组成的字符串中
满足k!=x且前k个字符和后k个字符组成的字符串相等的k的最大值
求pi和kmp算法如下(字符串匹配同样可以用hash)

void get_pi(){
    pi[0]=0;
    int k=0;
    repeat(i,1,s.length()){
        while(k>0 && s[i]!=s[k])
            k=pi[k-1];
        if(s[i]==s[k])
            k++;
        pi[i]=k;
    }
}

字符串匹配可以构造s=p+#+t

cin>>s1>>s2;
s=s2+' '+s1;
get_pi();
repeat(i,s2.size()+1,s.size())
if(pi[i]==s2.size())
    cout<<i-2*s2.size()+1<<endl;

Z函数,扩展kmp

z[x]表示原字符串与后(len-x)个字符组成的字符串中
满足它们的前k个字符组成的字符串相等的k的最大值
(即最大公共前缀长度)
z[0]是多少都无所谓

void get_z(){
    z[0]=0;
    int l=0,r=0;
    repeat(i,1,s.length()){
        if(i<=r)
            z[i]=min(r-i+1,z[i-l]);
        while(i+z[i]<s.length() && s[z[i]]==s[i+z[i]])
            ++z[i];
        if(i+z[i]-1>r)
            l=i,r=i+z[i]-1;
    }
}

字符串匹配可以构造s=p+#+t

字典树

struct trie{
    int tr[1000001][26],top;
    bool exist[1000001];
    void init(){
        top=1;
        mst(tr,0);
        mst(exist,0);
    }
    void insert(const char *s){
        int p=0;
        for(int i=0;s[i];i++){
            int c=s[i]-'a';
            if(!tr[p][c])tr[p][c]=top++;
            p=tr[p][c];
        }
        exist[p]=1;
    }
    int find(const char *s){
        int p=0;
        for(int i=0;s[i];i++){
            int c=s[i]-'a';
            if(!tr[p][c])return 0;
            p=tr[p][c];
        }
        return exist[p];
    }
};

马拉车

需要预处理,比如"12"->"%*1*2**"

len[0]=0;
mx=0;
id=0;
repeat(i,1,n-1){
    if(i<mx)len[i]=min(mx-i,len[2*id-i]);
    else len[i]=1;
    while(s[i-len[i]]==s[i+len[i]])len[i]++;
    if(len[i]+i>mx){
        mx=len[i]+i;
        id=i;
        //if(s[i]=='*')ans+=len[i]/2;
        //else ans+=(len[i]-1)/2;
    }
}

原文地址:https://www.cnblogs.com/axiomofchoice/p/11980504.html

时间: 2024-10-09 05:11:19

ACM模板的相关文章

ACM模板汇总

零散的算法模板最终将会整合在一起.之前整理过的模板已经传到github上,有多处错误且代码风格与现在我的不一致,懒得修改.今后会不断更新模板 模板传送门~

acm模板生成

为迎接,接下来的区域赛,要做好准备(虽然不是特别有信心,但是还是要鼓励自己,可以取得收获的,加油) acm_latex模板: https://www.cnblogs.com/palayutm/p/6444833.html#e69bb4e696b0_1 windows下安装texlive: https://blog.csdn.net/qq_38386316/article/details/80272396 等整理我们队的模板以后再发 原文地址:https://www.cnblogs.com/Dra

ACM模板——简单博弈

巴什博弈:只有一堆n个物品,两个人轮流从这堆物品中取物, 规定每次至少取一个,最多取m个.最后取光者得胜. if(n%(m+1)) first win else second win 巴什博弈 变种:取光者输 if(!(n-1)%(m+1)) second win else first win 巴什博弈变种 威佐夫博弈:有两堆各若干个物品,两个人轮流从任一堆取至少一个或同时从两堆中取同样多的物品,规定每次至少取一个,多者不限,最后取光者得胜. double r = (sqrt(5.0)+1)/2

ACM模板整理|2019/12/27

看到群里都是18.19级的学弟,才发现自己老了啊?? 还算充实的一天,就是电影还没看.. 最短路 Floyd 应用 1.Floyd求有向图最小环:枚举g[i][i] 2.Floyd求无向图最小环: if (f[k][i] && f[k][j] ) { ans = min(e[i][j]+2,ans); } const int inf = 0x3f3f3f3f; int g[MAX_N][MAX_N]; // 算法中的 G 矩阵 // 首先要初始化 g 矩阵 void init() { fo

ACM模板(持续补完)

1.KMP 1 #include<cstring> 2 #include<algorithm> 3 #include<cstdio> 4 using namespace std; 5 const int maxn=1e6; 6 char a[maxn+50],b[maxn+50]; 7 int f[maxn+50]; 8 int len1,len2,t; 9 int main() 10 { 11 scanf("%d\n",&t); 12 wh

ACM模板——分数类

#include <bits/stdc++.h> using namespace std; #define _for(i,a,b) for(int i = (a);i < (b);i ++) const int maxn = 50003; int gcd(int a,int b){if(b==0) return a;return gcd(b,a%b);} int lcm(int a,int b){return a/gcd(a,b)*b;} class Fraction { public:

ACM模板——最大公约数

int gcd(int a,int b) { return b == 0? a : gcd(b,a%b); } 欧几里得辗转相除法 //返回最大公约数 //ax+by=gcd(a,b),求x,y int exgcd(int a,int b,int &x,int &y) { int d = a; if(b != 0) { d = exgcd(b,a%b,y,x); y -= (a/b) * x; } else{x = 1;y = 0;} return d; } 扩展欧几里得 原文地址:htt

ACM模板——线段树&amp;树状数组

施工中 原文地址:https://www.cnblogs.com/Asurudo/p/10658452.html

ACM模板——取模

const ll mod = 1000000007; ll mult_mod(ll a,ll b) //(a*b)%mod a,b,mod<2^63 { a %= mod; b %= mod; ll ans=0; while(b) { if(b&1) { ans=ans+a; if(ans>=mod) ans=ans-mod; } a=a<<1; if(a>=mod) a=a-mod; b=b>>1; } return ans; } 积取模 const l

ACM模板——链式前向星&amp;&amp;邻接表存图

邻接表待补充 1 #include<bits/stdc++.h> 2 using namespace std; 3 #define MAXN 100501 4 struct NODE{ 5 int w; 6 int e; 7 int next; //next[i]表示与第i条边同起点的上一条边的储存位置 8 }edge[MAXN]; 9 int cnt; 10 int head[MAXN]; 11 void add(int u,int v,int w){ 12 edge[cnt].w=w; 1