ACM模板_axiomofchoice

目录

  • 语法
    • c++
    • java
  • 动态规划
    • 多重背包
    • 最长不下降子序列LIS
  • 计算几何
    • 向量(结构体)
    • 平面集合基本操作
    • 二维凸包
    • 旋转卡壳
    • 最大空矩形 | 扫描法
    • 平面最近点对 | 分治
    • 最小圆覆盖 | 随机增量法
    • 三维向量(结构体)
    • 三维凸包
    • 几何杂项
  • 数据结构
    • ST表
    • 单调队列
    • 树状数组
    • 线段树
    • 并查集
    • 左偏树
    • 珂朵莉树,老司机树
    • 二叉搜索树
    • 一些建议
  • 图论
    • 图论的一些概念
    • 最短路径相关
    • 最小生成树
    • 树论的一些概念
    • 最近公共祖先LCA
    • 联通性相关
    • 拓扑排序
    • 欧拉路径,回路
    • 图上的NP问题
    • 弦图,区间图 | 最大势算法MCS
    • 稳定婚姻 | 延迟认可
    • 仙人掌 | 圆方树
    • 二分图的一些概念
    • 二分图匹配
    • 前向星
    • 网络流
  • 字符串
    • KMP算法,前缀数组pi[]
    • Z函数,扩展kmp
    • 字典树Trie
    • 马拉车Manacher
  • 杂项
    • 位运算巨佬操作
    • 离散化
    • 枚举二进制子集
    • 质数表
    • 快读快写
    • 主定理
    • 01分数规划
    • 求逆序数 | 归并排序
    • 最大空矩阵 | 悬线法
    • 精确覆盖 | 舞蹈链
    • 高维前缀和
    • STL手写hash
    • 模拟退火SA
    • 对拍
    • 天坑
  • 数论基本操作
    • 模乘法 | 龟速乘
    • 最大公约数
    • 裴蜀定理 | 扩展欧几里得
    • 快速幂
    • 单个乘法逆元
    • 唯一分解
    • 单个欧拉函数
    • 素数判定
    • 大数分解 | Pollard-rho
    • 反素数问题
    • 数论分块
  • 筛法
    • 筛素数
    • 筛欧拉函数
    • 筛莫比乌斯函数
    • 杜教筛
  • 线性递推
    • 线性递推乘法逆元
    • 线性递推组合数
    • 线性递推二项式系数
  • 高级模运算
    • 线性同余方程组 | 中国剩余定理CRT+extra
    • 指标or离散对数 | 大步小步BSGS+extra
    • 阶与原根
    • N次剩余
    • 二次剩余
  • 特殊函数
    • 莫比乌斯反演
    • 斐波那契数列
  • 排列组合
    • 组合数取模 | 卢卡斯定理Lucas+extra
    • 编码与解码
    • 康托展开Cantor+逆
  • 浮点数误差分析
  • 博弈论
  • 多项式
    • 拉格朗日插值
    • 快速傅里叶变换FFT
  • 线性代数
    • 矩阵乘法、矩阵快速幂
    • 矩阵高级操作
    • 矩阵的一些结论
    • 线性基
  • 数学杂项
    • 约瑟夫问题
    • 格雷码和汉诺塔
    • 置换群计数
    • 一些简单的数学结论

语法

c++

这些宏定义其实是用来打cf的。编译环境:c++14

#include 
using namespace std; //using namespace rel_ops;
#define lll __int128
typedef long long ll; const int inf=~0u>>2; const ll INF=~0ull>>2;
typedef double lf; typedef long double llf; const lf err=1e-11; const lf pi=acos(-1);
typedef pair pii;
#define repeat(i,a,b) for(int i=(a),_=(b);i<_;i++)
#define repeat_back(i,a,b) for(int i=(b)-1,_=(a);i>=_;i--)
#define mst(a,x) memset(a,x,sizeof(a))
#define qwq (cerr<<"qwq"< &x){return o<<'('<
//如果没有万能头
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include

平板电视红黑树

#include 
#include 
using namespace __gnu_pbds;
tree,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且最小)
t.join(t2); //------------------------ 将t2并入t,t2清空,前提是取值范围不相交(所以没卵用)
t.split(v,t2); //--------------------- 小于等于v的元素属于t,其余的属于t2

平板电视优先队列

thin_heap_tag斐波那契堆,pairing_heap_tag配对堆

#include
using namespace __gnu_pbds;
__gnu_pbds::priority_queue,pairing_heap_tag> h; //大根堆
h.push(x); h.top(); h.pop();
h.join(h2); //将h2并入h,h2清空

块状链表rope

#include 
using namespace __gnu_cxx;
rope r; //块状链表
r.push_back(n);
r.insert(pos,n); //插入一个元素
r.erase(pos,len); //区间删除
r.copy(pos,len,x); //区间赋值
r.substr(pos,len); //这是啥
r[pos] //只能访问不能修改
rope *his[N]; his[i]=new rope(*his[i-1]); //据说O(1)拷贝,一行可持久化
//空……空缺

随机数mt19937

#include
mt19937 rnd(time(0));
cout<

浮点数常数

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

STL汇总

将第k+1小的数置于k这个位置:nth_element(begin,begin+k,end);
返回是否存在:binary_search(begin,end,key,[less])
返回限制最小值地址:upper_bound(begin,end,key,[less])
返回严格限制最小值地址:lower_bound(begin,end,key,[less])
填充:fill(begin,end,element);
递增填充(赋值为t,t+1,t+2,...):iota(begin,end,t);
复制(注意会复制到b里):copy(a_begin,a_end,b_begin);
翻转:reverse(begin,end);
单次归并排序(合并有序列表a,b至c):merge(a_begin,a_end,b_begin,b_end,c_begin,[less]);
inplace_merge(begin,begin+k,end);
vector去重(之前要排序):a.erase(unique(a.begin(),a.end()),a.end());
vector释放额外内存:a.shrink_to_fit();
并集(结果存c):set_union(a_begin,a_end,b_begin,b_end,c_begin);
set,map查找,没找到返回end():a.find(key)
set,map限制最小值:a.lower_bound(key)
set,map合并:a.insert(b.begin(),b.end()); //O(nlogn)
complex c; complex c(1,2);//复数
c.real(),c.imag() //实部、虚部
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位

面向对象

struct vi:vector{ //继承
    explicit vi(int){} //禁止隐式转换的构造函数
};

吸氧气

#pragma GCC optimize(2)
#pragma GCC optimize(3)
#pragma GCC optimize("Ofast")

进制转换

itoa(n,str,base) //将n存入字符数组中(只能int)(不一定能用,cf不行)
strtol(str,0,base),strtoll //返回字符数组的base进制数
stol(s,0,base),stoll //返回字符串的base进制数
sscanf,sprintf //十进制
to_string(n) //十进制

位运算函数(不需要头文件)

__builtin_ctz(x),__builtin_ctzll(x) //返回x后导0的个数,x是0则返回32 or 64
__builtin_clz(x),__builtin_clzll(x) //返回x前导0的个数,x是0则返回32 or 64
__builtin_popcount(x),__builtin_popcountll(x) //返回x中1的个数
__builtin_parity(x),__builtin_parityll(x) //返回x中1的个数是否为奇数

cmath

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

双关键字比较

不如用 make_pair(a.x,a.y)
int类型还可以 pii(a.x,a.y)

用了命名空间rel_ops就能定义小于就能用其他所有不等号

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 => (c++) const
var n=1; //var => (c++) auto

long 型常量结尾加 L,如 1L
空指针 null

数组

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]; //二维数组
Array.sort(arr,l,r); //对arr[l..(r-1)]排序(import java.util.Arrays;)

输出

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(); //读实数
sc.hasNext() //是否读完

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

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)

class

class node{
    int l,r;
} //不用分号
//之后在class Main里定义static node name

BigInteger

import java.math.BigInteger;
BigInteger n=new BigInteger("0");
BigInteger[] arr=new BigInteger[10];
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
n.compareTo(n2) //n>n2返回1,n

BigDecimal

import java.math.BigDecimal;
n.divide(n2,2,BigDecimal.ROUND_HALF_UP) //保留两位(四舍五入)
//貌似没有sqrt等操作,都得自己实现qwq

动态规划

多重背包

二进制版,\(O(mn\log\max\{num\})\)

//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

二分查找优化,\(O(n\log n)\)

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;

计算几何

向量(结构体)

struct vec{
    lf x,y;
    vec(lf x=0,lf y=0):x(x),y(y){}
    vec operator-(const vec &b){return vec(x-b.x,y-b.y);}
    vec operator+(const vec &b){return vec(x+b.x,y+b.y);}
    vec operator*(lf k){return vec(k*x,k*y);}
    lf len(){return hypot(x,y);}
    lf sqr(){return x*x+y*y;}
    /*截取*/vec trunc(lf k=1){return *this*(k/len());}
    /*逆时针旋转*/vec rotate(double th){lf c=cos(th),s=sin(th); return vec(x*c-y*s,x*s+y*c);}
}a[N];
lf cross(vec a,vec b){return a.x*b.y-a.y*b.x;};
lf cross(vec a,vec b,vec c){return cross(a-b,b-c);}
lf dot(vec a,vec b){return a.x*b.x+a.y*b.y;}
bool cmp_xy(const vec &a,const vec &b){return make_pair(a.x,a.y)

平面集合基本操作

判断两条线段是否相交

快速排斥实验:判断线段所在矩形是否相交(用来减小常数,可省略)
跨立实验:任一线段的两端点在另一线段的两侧

bool judge(vec a,vec b,vec c,vec d){ //线段ab和线段cd
    #define SJ(x) max(a.x,b.x)

点是否在线段上

bool onseg(vec p,vec a,vec b){
    return (a.x-p.x)*(b.x-p.x)

多边形面积

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

多边形的面积质心

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

二维凸包

求上凸包,按坐标 \((x,y)\) 字典升序排序,从小到大加入栈,如果出现凹多边形情况则出栈。下凸包反着来
\(O(n\log n)\),排序是瓶颈

//struct vec{构造函数,减法,小于};
lf cross(vec a,vec b){return a.x*b.y-a.y*b.x;}
lf cross(vec a,vec b,vec c){return cross(a-b,b-c);}
vector st;
void push(vec &v,int b){
    while(st.size()>b
    && cross(*++st.rbegin(),st.back(),v)<=0) //会得到逆时针的凸包
        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],1);
    int b=st.size();
    repeat_back(i,1,n-1)push(a[i],b);
    //repeat_back自动变成上凸包
}

旋转卡壳

每次找到凸包每条边的最远点,基于二维凸包,\(O(n\log n)\)

lf calipers(vec a[],int n){
    convex(a,n); //凸包算法
    repeat(i,0,st.size())a[i]=st[i]; n=st.size();
    lf ans=0;
    int p=1;
    a[n]=a[0];
    repeat(i,0,n){
        while(cross(a[p],a[i],a[i+1])

最大空矩形 | 扫描法

在范围 \((0,0)\)\((l,w)\) 内求面积最大的不覆盖任何点的矩形面积,\(O(n^2)\)\(n\) 是点数
如果是 lf 就把 vec 结构体内部、ansud 的类型改一下

struct vec{
    int x,y; //可能是lf
    vec(int x,int y):x(x),y(y){}
};
vector a; //存放点
int l,w;
int ans=0;
void work(int i){
    int u=w,d=0;
    repeat(k,i+1,a.size())
    if(a[k].y>d && a[k].ya[i].y?u:d)=a[k].y; //更新u和d
        if((l-a[i].x)*(u-d)<=ans)return; //最优性剪枝
    }
    ans=max(ans,(l-a[i].x)*(u-d)); //撞墙更新ans
}
int query(){
    a.push_back(vec(0,0));
    a.push_back(vec(l,w)); //加两个点方便处理
    //小矩形的左边靠着顶点的情况
    sort(a.begin(),a.end(),[](vec a,vec b){return a.x

平面最近点对 | 分治

\(O(n\log n)\)

lf ans;
bool cmp_y(vec a,vec b){return a.yans)break;
            upd(a[i],b[j]);
        }
        b[t++]=a[i];
    }
}
lf nearest(){
    ans=1e20;
    sort(a,a+n); //按x排序
    rec(0,n);
    return ans;
}

最小圆覆盖 | 随机增量法

eps可能要非常小。随机化,均摊 \(O(n)\)

struct cir{ //圆(结构体)
    vec v; lf r;
    bool out(vec a){ //点a在圆外
        return (v-a).len()>r+err;
    }
    cir(vec a){ //点圆
        v=a;
        r=0;
    }
    cir(vec a,vec b){ //确定直径的圆
        v=(a+b)*0.5;
        r=(v-a).len();
    }
    cir(vec a,vec b,vec c){ //三个点的外接圆
        b=b-a,c=c-a;
        vec s=vec(b.sqr(),c.sqr())*0.5;
        lf d=1/cross(b,c);
        v=a+vec(s.x*c.y-s.y*b.y,s.y*b.x-s.x*c.x)*d;
        r=(v-a).len();
    }
};
cir RIA(vec *a,int n){ //RIA=随机增量法
    repeat_back(i,2,n)swap(a[rand()%i],a[i]); //random_shuffle(a,a+n);
    cir c=cir(a[0]);
    repeat(i,1,n)
    if(c.out(a[i])){
        c=cir(a[i]);
        repeat(j,0,i)
        if(c.out(a[j])){
            c=cir(a[i],a[j]);
            repeat(k,0,j)
            if(c.out(a[k]))
                c=cir(a[i],a[j],a[k]);
        }
    }
    return c;
}

三维向量(结构体)

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)

三维凸包

将所有凸包上的面放入面集 f 中,其中 face::p[i] 作为 a 的下标,\(O(n^2)\)

const lf err=1e-9;
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]]);
    }
    lf area(){return normal().len()/2.0;}
};
vector 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 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);
    }
}

几何杂项

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

曼: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

正幂反演

给定反演中心 \(O\) 和反演半径 \(R\)。若直线上的点 \(OPQ\) 满足 \(|OP|\cdot|OQ|=R^2\),则 \(P\)\(Q\) 互为反演点
不经过反演中心的圆的反演图形是圆(计算时取圆上靠近/远离中心的两个点)
经过反演中心的圆的反演图形是直线(计算时取远离中心的点,做垂线)

数据结构

ST表

利用倍增实现对区间的查询操作,初始化 \(O(n\log n)\),查询 \(O(1)\)
编号从 \(0\) 开始

//RMQ=区间最值查询问题
struct RMQ{ //注意logN=log(N)+2
    #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(){
        repeat(i,0,n)
            f[i][0]=a[i];
        repeat(k,1,logN)
        repeat(i,0,n-(1<

补:二维st表

struct RMQ{ //注意logN=log(N)+2
    #define logN 9
    #define op(x,y) max(x,y)
    int f[N][N][logN][logN],log[N];
    RMQ(){
        log[1]=0;
        repeat(i,2,N)
            log[i]=log[i/2]+1;
    }
    void build(){
        repeat(k,0,logN)
        repeat(l,0,logN)
        repeat(i,0,n-(1<

单调队列

求所有长度为k的区间中的最小值,完整跑下来 \(O(n)\)

struct MQ{ //查询就用mq.q.front().first
    deque 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;

树状数组

单点+区间,修改查询 \(O(\log n)\)

#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;
    }
}bit;

补充:超级树状数组
基于树状数组,基本只允许加法,区间+区间,修改查询 \(O(\log n)\)

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));
    }
}spbit;

补充:二维树状数组和超级树状数组,修改查询 \(O(\log n\cdot\log m)\)

int n,m;
#define lb(x) (x&(-x))
#define ll int
struct BIT{
    ll t[N][N]; //一倍内存吧
    void init(){
        mst(t,0);
    }
    void add(int x,int y,ll k){ //位置(x,y)加上k
        //x++,y++; //如果要从0开始编号
        for(int i=x;i<=n;i+=lb(i))
        for(int j=y;j<=m;j+=lb(j))
            t[i][j]+=k;
    }
    ll sum(int x,int y){ //求(1..x,1..y)的和
        //x++,y++; //如果要从0开始编号
        ll ans=0;
        for(int i=x;i!=0;i-=lb(i))
        for(int j=y;j!=0;j-=lb(j))
            ans+=t[i][j];
        return ans;
    }
};
struct SPBIT{
    BIT a,ax,ay,axy;
    void add(int x,int y,int k){
        a.add(x,y,k);
        ax.add(x,y,k*x);
        ay.add(x,y,k*y);
        axy.add(x,y,k*x*y);
    }
    ll sum(int x,int y){
        return a.sum(x,y)*(x*y+x+y+1)
            -ax.sum(x,y)*(y+1)
            -ay.sum(x,y)*(x+1)
            +axy.sum(x,y);
    }
    void add(int x0,int y0,int x1,int y1,int k){ //区间修改
        add(x0,y0,k);
        add(x0,y1+1,-k);
        add(x1+1,y0,-k);
        add(x1+1,y1+1,k);
    }
    ll sum(int x0,int y0,int x1,int y1){ //区间查询
        return sum(x1,y1)
            -sum(x0-1,y1)
            -sum(x1,y0-1)
            +sum(x0-1,y0-1);
    }
}spbit;

线段树

数组版

区间+区间,修改查询 \(O(\log n)\)

#define zero 0
struct seg{ //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(ly){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;

指针版

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

#define zero 0
struct seg{
    int data,lazy,l,r;
    seg *lc,*rc;
    seg(int l,int r):l(l),r(r){
        lazy=zero;
        if(l==r){
            data=a[l];
            return;
        }
        int m=(l+r)/2;
        lc=new seg(l,m);
        rc=new seg(m+1,r);
        up();
    }
    void up(){
        data=lc->data+rc->data;
    }
    void down(){
        if(llazy+=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 seg(1,n);

带内存池指针版

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

#define zero 0
struct seg{
    int data,lazy,l,r;
    seg *lc,*rc;
    seg(int,int);
    seg(){};
    void up(){
        data=lc->data+rc->data;
    }
    void down(){
        if(llazy+=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;
seg pool[200010],*poolt; //两倍内存
seg::seg(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=seg(l,m);
    rc=poolt++; *rc=seg(m+1,r);
    up();
}
//初始化poolt=pool; st=poolt++; *st=seg(1,n);

zkw线段树

单点+区间,编号从0开始,建树 \(O(n)\) 修改查询 \(O(\log n)\)

struct seg{
    static const int N=16; //N=2^k且大于等于n
    typedef ll T;
    #define op(a,b) max(a,b)
    T zero=0;
    T a[N*2+10]; //两倍内存
    void build(T a0[],int n){ //建树
        copy(a0,a0+n,a+N);
        repeat_back(i,1,N)up(i);
        //for(int l=(N-1)>>1,r=(N+n)>>1;l^r^1;l>>=1,r>>=1)repeat(i,l+1,r)up(i); //重复建树
    }
    inline void up(int x){
        a[x]=op(a[x<<1],a[(x<<1)^1]);
    }
    void update(int x,T k){ //位置x加上k
        a[x+=N]+=k; //也可以赋值等操作
        while(x>>=1)up(x);
    }
    T query(int l,int r){ //区间查询
        T ans=zero;
        for(l+=N-1,r+=N+1;l^r^1;l>>=1,r>>=1){
            if(~l & 1)ans=op(ans,a[l^1]); //l^1其实是l+1
            if(r & 1)ans=op(ans,a[r^1]); //r^1其实是r-1
        }
        return ans;
    }
}zkw;

并查集

合并查找 \(O(α(n))\),可视为 \(O(1)\)

精简版,只有路径压缩

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

普通版,路径压缩+按秩合并

struct DSU{
    int a[10010],sz[10010];
    void init(int n){
        iota(a,a+n+1,0);
        fill(sz,sz+n+1,1);
    }
    int fa(int x){
        return a[x]==x?x:a[x]=fa(a[x]);
    }
    bool query(int x,int y){ //查找
        return fa(x)==fa(y);
    }
    void join(int x,int y){ //合并
        x=fa(x),y=fa(y);
        if(x==y)return;
        if(sz[x]>sz[y])swap(x,y);
        a[x]=y;
        sz[y]+=sz[x];
    }
}d;

补:种类并查集

struct DSU{
    int a[50010],r[50010];
    void init(int n){
        repeat(i,0,n+1)a[i]=i,r[i]=0;
    }
    int plus(int a,int b){ //关系a+关系b,类似向量相加
        if(a==b)return -a;
        return a+b;
    }
    int inv(int a){ //关系a的逆
        return -a;
    }
    int fa(int x){ //返回根节点 
        if(a[x]==x)return x;
        int f=a[x],ff=fa(f);
        r[x]=plus(r[x],r[f]);
        return a[x]=ff;
    }
    bool query(int x,int y){ //是否存在关系
        return fa(x)==fa(y);
    }
    int getr(int x,int y){ //查找关系
        return plus(r[x],inv(r[y]));
    }
    void join(int x,int y,int r2){ //按r2关系合并
        r2=plus(plus(inv(r[x]),r2),r[y]);
        x=fa(x),y=fa(y);
        a[x]=y,r[x]=r2;
    }
}d;

左偏树

万年不用,\(O(?)\)如果没有特殊要求一律平板电视

struct leftist{ //编号从1开始,因为空的左右儿子会指向0
    #define lc LC[x]
    #define rc RC[x]
    vector 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]

珂朵莉树,老司机树

珂朵莉数以区间形式存储数据,非常暴力,适用于有区间赋值操作的题
均摊 \(O(n\log\log n)\),但是可能被卡

struct ODT{
    struct node{
        int l,r;
        mutable int v; //强制可修改
        bool operator<(const node &b)const{return l a;
    void init(){ //初始化
        a.clear();
        a.insert({-inf,inf,0});
    }
    auto split(int x){ //分裂区间
        auto it=--a.upper_bound({x,0,0});
        if(it->l==x)return it;
        int l=it->l,r=it->r,v=it->v;
        a.erase(it);
        a.insert({l,x-1,v});
        return a.insert({x,r,v}).first;
    }
    void assign(int l,int r,int v){ //区间赋值
        auto y=split(r+1),x=split(l);
        a.erase(x,y);
        a.insert({l,r,v});
    }
    int sum(int l,int r){ //操作示例:区间求和
        auto y=split(r+1),x=split(l);
        int ans=0;
        for(auto i=x;i!=y;i++){
            ans+=(i->r-i->l+1)*i->v;
        }
        return ans;
    }
}odt;

二叉搜索树

左子树所有结点 \(\le v <\) 右子树所有节点,目前仅支持插入,查询可以写一个 map

struct TR{
    TR *ch[2],*fa; //ch[0]左儿子,ch[1]右儿子,fa父亲,根的父亲是inf
    int v,dep; //v是结点索引,dep深度,根的深度是1
    TR(TR *fa,int v,int dep):fa(fa),v(v),dep(dep){
        mst(ch,0);
    }
    void insert(int v2){ //tr->insert(v2)插入结点
        auto &c=ch[v2>v];
        if(c==0)c=new TR(this,v2,dep+1);
        else c->insert(v2);
    }
}*tr=new TR(0,inf,0);
//inf是无效节点,用tr->ch[0]来访问根节点

一些建议

双头优先队列可以用multiset

双关键字堆可以用两个multiset模拟

struct HEAP{
    multiset 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);
    }
};

图论

图论的一些概念

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

正则图:所有点的度均相同的无向图
强正则图:与任意两个相邻的点相邻的点数相同,与任意两个不相邻的点相邻的点数相同的正则图
强正则图的点数 \(v\),度 \(k\),相邻的点的共度 \(\lambda\),不相邻的点的共度 \(\mu\)\(k(k-1-\lambda)=\mu(v-1-k)\)
强正则图的例子:所有完全图、所有nk顶点满n分图、五边形镶嵌一个五角星

点联通度:最小点割集的大小
边联通度:最小边割集的大小
Whitney定理:点联通度≤边联通度≤最小度

最大团:最大完全子图
最大独立集:最多的两两不连接的顶点
最大独立集即补图最大团

最小染色数:相邻的点不同色的最少色数
最小团覆盖数:覆盖整个图的最少团数
最小染色数等于补图最小团覆盖数

哈密顿通路:通过所有顶点有且仅有一次的路径,若存在则为半哈密顿图/哈密顿图
哈密顿回路:通过所有顶点有且仅有一次的回路,若存在则为哈密顿图
完全图 \(K_{2k+1}\) 的边集可以划分为 \(k\) 个哈密顿回路
完全图 \(K_{2k}\) 的边集去掉 \(k\) 条互不相邻的边后可以划分为 \(k-1\) 个哈密顿回路

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

最短路径相关

Dijkstra

\(O((V+E)\log V)\)

struct node{
    int to,dis;
    bool operator<(node b)const{
        return dis>b.dis;
    }
};
void dijkstra(int s){//s是起点
    repeat(i,0,n){
        vis[i]=0;
        dis[i]=2147483647;
    }
    dis[s]=0; //last[s]=-1;
    q=priority_queue();
    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; //last可以记录最短路(倒着)
            }
        }
    }
}

Floyd

\(O(V^3)\)

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]);

补充:bitset 优化(只考虑是否可达),\(O(V^3)\)

//bitset g;
repeat(i,0,n)
repeat(j,0,n)
if(g[j][i])
    g[j]|=g[i];

SPFA

SPFA搜索中,有一个点入队 \(n\) 次即存在负环
邻接表从 \(0\) 开始编号,\(O(VE)\)

int n;
struct node{int to,dis;};
vector e[N];
int cnt[N],inque[N],vis[N],dis[N];
bool spfa(int s){ //返回是否有负环(s为起点)
    queue q; q.push(s); inque[s]=0;
    cnt[s]=1; dis[s]=0; //last[s]=-1;
    while(!q.empty()){
        int x=q.front(); q.pop(); inque[x]=0; //出队
        vis[x]=1;
        for(auto i:e[x]){
            int p=i.to;
            if(dis[p]>dis[x]+i.dis){
                dis[p]=dis[x]+i.dis;
                //last[p]=x; //last可以记录最短路(倒着)
                if(inque[p])continue;
                q.push(p); inque[p]=1; //入队(求最短路可以SLF优化)
                if(++cnt[p]>n)return 1;
            }
        }
    }
    return 0;
}
bool solve(){ //返回是否有负环
    fill(cnt,cnt+n,0);
    fill(dis,dis+n,inf);
    fill(inque,inque+n,0);
    fill(vis,vis+n,0);
    repeat(i,0,n)
    if(!vis[i] && spfa(i))
        return 1;
    return 0;
}

最小环

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

最小生成树

Kruskal

对边长排序,然后添边,并查集判联通,\(O(E\log E)\),排序是瓶颈

DSU d;
struct edge{int u,v,dis;}e[200010];
ll kru(){
    ll 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)continue;
        d.join(x,y);
        ans+=e[i].dis;
        cnt++;
        if(cnt==n-1)break;
    }
    if(cnt!=n-1)return -1;
    else return ans;
}

Boruvka

类似Prim算法,但是可以多路增广(名词迷惑行为),\(O(E\log V)\)

DSU d;
struct edge{int u,v,dis;}e[200010];
ll bor(){
    ll ans=0;
    d.init(n);
    e[m].dis=inf;
    vector b; //记录每个联通块的增广路(名词迷惑行为)
    bool f=1;
    while(f){
        b.assign(n,m);
        repeat(i,0,m){
            int x=d[e[i].u],y=d[e[i].v];
            if(x==y)continue;
            if(e[i].dis

最小树形图 | 朱刘算法

其实有更高级的Tarjan算法 \(O(E+V\log V)\)但是学不会
编号从1开始,求的是叶向树形图,\(O(VE)\)

int n;
struct edge{int x,y,w;};
vector eset; //会在solve中被修改
ll solve(int rt){ //返回最小的边权和,返回-1表示没有树形图
    static int fa[N],id[N],top[N],minw[N];
    ll ans=0;
    while(1){
        int cnt=0;
        repeat(i,1,n+1)
            id[i]=top[i]=0,minw[i]=inf;
        for(auto &i:eset) //记录权最小的父亲
        if(i.x!=i.y && i.w

树论的一些概念

树的直径

直径:即最长路径

以任意一点出发所能达到的最远节点为一个端点
以这个端点出发所能达到的最远节点为另一个端点
两次Dijkstra(其实只是dfs,维护 vis[]dis[]

也可以树上dp

树的重心

重心:以重心为根,其最大儿子子树最小

DFS计算所有子树大小
最后计算最大儿子子树最小的节点

最近公共祖先LCA

欧拉序列RMQ版本

编号从 \(0\) 开始,初始化 \(O(n\log n)\),查询 \(O(1)\)

int n,m;
vector a;
vector e[500010];
bool vis[500010];
int pos[500010],dep[500010];
#define mininarr(a,x,y) (a[x]r)swap(l,r);//!!
        int s=log[r-l+1];
        return mininarr(dep,f[l][s],f[r-(1<

好多LCA算法都没学qwq

空缺

联通性相关

强联通分量+缩点

Tarjan,\(O(V+E)\)

stack stk;
bool vis[N],instk[N];
int dfn[N],low[N],co[N],w[N]; //co:染色结果
vector sz; //第i个分量的点数
int dcnt; //时间戳
void dfs(int x){ //Tarjan求强联通分量
    if(vis[x])return; vis[x]=1;
    stk.push(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.top();
            stk.pop();
            instk[t]=0;
            sz.back()+=w[t]; //记录
            co[t]=sz.size()-1; //染色
        }while(t!=x);
    }
}
void shrink(){ //缩点
    set eset;
    fill(vis,vis+n,0);
    //fill(instk,instk+n,0);
    sz.clear();
    eset.clear();
    dcnt=0;
    repeat(i,0,n)
    if(!vis[i])
        dfs(i);
    repeat(i,0,n)
    for(auto p:a[i])
    if(co[i]!=co[p])
        eset.insert({co[i],co[p]});
    n=sz.size();
    repeat(i,0,n){
        a[i].clear();
        w[i]=sz[i];
    }
    for(auto i:eset){
        a[i.fi].push_back(i.se);
        //a[i.se].push_back(i.fi);
    }
}

割点

Tarjan

bool vis[N],cut[N]; //cut即结果,cut[i]表示i是否为割点
int dfn[N],low[N];
int dcnt; //时间戳
void dfs(int x,bool isroot=1){
    if(vis[x])return; vis[x]=1;
    dfn[x]=low[x]=++dcnt;
    int ch=0; cut[x]=0;
    for(auto p:a[x]){
        if(!vis[p]){
            dfs(p,0);
            low[x]=min(low[x],low[p]);
            if(!isroot && low[p]>=dfn[x])
                cut[x]=1;
            ch++;
        }
        low[x]=min(low[x],dfn[p]);
    }
    if(isroot && ch>=2) //根节点判断方法
        cut[x]=1;
}

拓扑排序

\(O(V+E)\)

queue q;
int ideg[N];
vector 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)

图上的NP问题

最大团,极大团计数

求最大团顶点数(和最大团),g[][] 编号从 \(0\) 开始,\(O(\exp)\)

int g[N][N],f[N][N],v[N],Max[N],n,ans; //g[][]是邻接矩阵,n是顶点数
//vector rec,maxrec; //maxrec是最大团
bool dfs(int x,int cur){
    if(cur==0)
        return x>ans;
    repeat(i,0,cur){
        int u=f[x][i],k=0;
        if(Max[u]+x<=ans)return 0;
        repeat(j,i+1,cur)
        if(g[u][f[x][j]])
            f[x+1][k++]=f[x][j];
        //rec.push_back(u);
        if(dfs(x+1,k))return 1;
        //rec.pop_back();
    }
    return 0;
}
void solve(){
    ans=0; //maxrec.clear();
    repeat_back(i,0,n){
        int k=0;
        repeat(j,i+1,n)
        if(g[i][j])
            f[1][k++]=j;
        //rec.clear(); rec.push_back(i);
        if(dfs(1,k)){
            ans++;
            //maxrec=rec;
        }
        Max[i]=ans;
    }
}

求极大团个数(和所有极大团),g[][] 的编号从 \(1\) 开始!\(O(\exp)\)

int g[N][N],n;
//vector rec; //存当前极大团
int ans,some[N][N],none[N][N]; //some是未搜索的点,none是废除的点
void dfs(int d,int sn,int nn){
    if(sn==0 && nn==0)
        ans++; //此时rec是其中一个极大图
    //if(ans>1000)return; //题目要求_(:зゝ∠)_
    int u=some[d][0];
    for(int i=0;i

最小染色数,最小独立集覆盖

\(O(\exp)\)n=17 可用

int n,m;
int g[N]; //二进制邻接矩阵
bool ind[1< a; //存独立集
#define np (1<(); q.push(0);
    while(!q.empty()){
        int x=q.front(); q.pop();
        for(auto i:a){
            int p=x|i;
            if(p==np-1)return dis[x]+1;
            if(dis[p]>dis[x]+1){
                dis[p]=dis[x]+1;
                q.push(p);
            }
        }
    }
    return 0;
}
int solve(){ //返回最小染色数
    mst(g,0);
    for(auto i:eset){
        int x=i.fi,y=i.se;
        g[x]|=1<

弦图,区间图 | 最大势算法MCS

弦是连接环上不相邻点的边;弦图是所有长度大于3的环都有弦的无向图(类似三角剖分)
单纯点:所有与v相连的点构成一个团,则v是一个单纯点
完美消除序列:即点集的一个排列 \([v_1,v_2,...,v_n]\) 满足任意 \(v_i\)\([v_{i+1},...,v_n]\) 的导出子图中是一个单纯点
定理:无向图是弦图 \(\Leftrightarrow\) 无向图存在完美消除序列
定理:最大团顶点数 \(\le\) 最小染色数(弦图取等号)
定理:最大独立集顶点数 \(\le\) 最小团覆盖(弦图取等号)

最大势算法MCS求完美消除序列:每次求出与 \([v_{i+1},...,v_n]\) 相邻点数最大的点作为 \(v_i\)
e[][]点编号从 \(1\) 开始!rec 下标从 \(1\) 开始!桶优化,\(O(V+E)\)

vector e[N];
int n,rec[N]; //rec[1..n]是结果
int h[N],nxt[N],pre[N],vis[N],lab[N];
void del(int x){
    int w=lab[x];
    if(h[w]==x)h[w]=nxt[x];
    pre[nxt[x]]=pre[x];
    nxt[pre[x]]=nxt[x];
}
void mcs(){
    fill(h,h+n+1,0);
    fill(vis,vis+n+1,0);
    fill(lab,lab+n+1,0);
    iota(nxt,nxt+n+1,1);
    iota(pre,pre+n+1,-1);
    nxt[n]=0;
    h[0]=1;
    int w=0;
    repeat_back(i,1,n+1){
        int x=h[w];
        rec[i]=x;
        del(x);
        vis[x]=1;
        for(auto p:e[x])
        if(!vis[p]){
            del(p);
            lab[p]++;
            nxt[p]=h[lab[p]];
            pre[h[lab[p]]]=p;
            h[lab[p]]=p;
            pre[p]=0;
        }
        w++;
        while(h[w]==0)w--;
    }
}

判断弦图(判断是否为完美消除序列):对所有 \(v_i\)\([v_{i+1},...,v_n]\) 中与 \(v_i\) 相连的最靠前一个点 \(v_j\) 是否与与 \(v_i\) 连接的其他点相连
编号规则同上,大佬:\(O(V+E)\),我:\(O((V+E)\log V)\)

bool judge(){ //返回是否是完美消除序列(先要跑一遍MCS)
    static int s[N],rnk[N];
    repeat(i,1,n+1){
        rnk[rec[i]]=i;
        sort(e[i].begin(),e[i].end()); //方便二分查找,内存足够直接unmap
    }
    repeat(i,1,n+1){
        int top=0,x=rec[i];
        for(auto p:e[x])
        if(rnk[x]

其他弦图算法

int color(){ //返回最大团点数/最小染色数
    return *max_element(lab+1,lab+n+1)+1;
    /* //以下求最大团
    static int rnk[N];
    repeat(i,1,n+1)rnk[rec[i]]=i;
    int x=max_element(lab+1,lab+n+1)-lab;
    rec2.push_back(x);
    for(auto p:e[x])
    if(rnk[x]0 && cnt[x]>=cnt[fst[x]]+1)
            vis[fst[x]]=1;
    }
    return ans;
}

区间图:给出的每个区间都看成点,有公共部分的两个区间之间连一条边
区间图是弦图(反过来不一定),可以应用弦图的所有算法
区间图的判定:所有弦图可以写成一个极大团树(所有极大团看成一个顶点,极大团之间有公共顶点就连一条边),区间图的极大团树是一个链

稳定婚姻 | 延迟认可

稳定意味着不存在一对不是情侣的男女,都认为当前伴侣不如对方
编号从 \(0\) 开始,\(O(n^2)\)

struct node{
    int s[N]; //s的值给定
        //对男生来说是女生编号排序
        //对女生来说是男生的分数
    int now; //选择的伴侣编号
}a[N],b[N]; //男生,女生
int tr[N]; //男生尝试表白了几次
queue q; //单身狗(男)排队
bool match(int x,int y){ //配对,返回是否成功
    int x0=b[y].now;
    if(x0!=-1){
        if(b[y].s[x]();
    repeat(i,0,n){
        b[i].now=-1;
        q.push(i);
        tr[i]=0;
    }
    while(!q.empty()){
        int x=q.front(); q.pop();
        int y=a[x].s[tr[x]++]; //下一个最中意女生
        if(!match(x,y))
            q.push(x); //下次努力
    }
}

仙人掌 | 圆方树

仙人掌:所有边只出现在一个简单环的联通图
圆方树:原来的点称为圆点,环上的圆点都与新建的方点连接
子仙人掌:以 \(r\) 为根,点 \(p\) 的子仙人掌是删掉 \(p\)\(r\) 的所有简单路径后 \(p\) 所在的联通块。这个子仙人掌就是圆方树中以 \(r\) 为根时,\(p\) 子树中的所有圆点

二分图的一些概念

最小点覆盖 = 最大匹配
最大独立集 = 顶点数 - 最大匹配
最小路径覆盖 = (开点前)顶点数 - 最大匹配,右顶点未被匹配的都看作起点

二分图匹配

匈牙利,\(O(VE)\)

int n,m; //n个左顶点,m个右顶点
vector a[N]; //左顶点x与右顶点a[x][0..sz]有连接
int dcnt,mch[N],dfn[N]; //mch[p]表示左顶点mch[p]与右顶点p的连接被选中,dfn[p]==dcnt意味着右顶点p已访问
bool dfs(int x){
    repeat(i,0,a[x].size()){
        int p=a[x][i];
        if(dfn[p]!=dcnt){
            dfn[p]=dcnt;
            if(mch[p]==-1 || dfs(mch[p])){
                mch[p]=x;
                return true;
            }
        }
    }
    return false;
}
int hungarian(){ //返回最大匹配数
    int ans=0;
    repeat(i,0,m)mch[i]=dfn[i]=-1; //初始化
    repeat(i,0,n){
        dcnt=i;
        if(dfs(i))ans++;
    }
    return ans;
}

网络流建图跑Dinic,基于Dinic,\(O(VE^{\tfrac 1 2})\)

void ae(int x,int y){
    addedge(x,y,1);
    addedge(y,x,0);
}
cin>>n1>>n2>>m; //左顶点数,右顶点数,之间的边数
n=n1+n2+2; //网络顶点数
s=0,t=n1+n2+1; //源点和汇点
init();
repeat(i,1,n1+1)ae(s,i); //构造源点与左顶点的边
repeat(i,n1+1,n1+n2+1)ae(i,t); //构造右顶点与汇点的边
repeat(i,0,m){
    int x,y; cin>>x>>y; x--,y--;
    ae(x+1,n1+y+1); //构造左顶点与右顶点的边
}
cout<

前向星

struct edge{int to,w,nxt;}; //指向,权值,下一条边
vector a;
int head[N];
void addedge(int x,int y,int w){
    a.push_back((edge){y,w,head[x]});
    head[x]=a.size()-1;
}
void init(int n){ //初始化
    a.clear();
    fill(head,head+n,-1);
}
//for(int i=head[x];i!=-1;i=a[i].nxt) //遍历x出发的边(x,a[i].to)

网络流

一些概念

费用流(最小费用最大流):保证最大流后的最小费用
割:一个边集,删除割后最大流为0。边集的容量和为割的容量
最大流最小割定理:最大流即最小割容量

二分图最小带权点覆盖 = 点权之和 - 最大带权独立集(最小割)

最大流

以下顶点编号均从 \(0\) 开始

Dinic,多路增广,\(O(V^2E)\),然而并没有封装起来,ISAP不香吗

struct edge{int to,w,nxt;}; //指向,限流,下一条边
vector a;
int head[N],cur[N];
void addedge(int x,int y,int w){
    a.push_back((edge){y,w,head[x]});
    head[x]=a.size()-1;
}
int n,m,s,t; //点数,边数,源点,汇点
queue q;
bool inque[N]; //在队里的不需要入队
int dep[N]; //深度
bool bfs(){ //记录深度
    fill(dep,dep+n,inf); dep[s]=0;
    copy(head,head+n,cur); //当前弧初始化
    q=queue(); q.push(s);
    while(!q.empty()){
        int x=q.front(); q.pop();
        inque[x]=0;
        for(int i=head[x];i!=-1;i=a[i].nxt){
            int p=a[i].to;
            if(dep[p]>dep[x]+1 && a[i].w){
                dep[p]=dep[x]+1;
                if(inque[p]==0){
                    inque[p]=1;
                    q.push(p);
                }
            }
        }
    }
    return dep[t]!=inf;
}
int dfs(int x,int flow){
    int now,ans=0;
    if(x==t)return flow;
    for(int i=cur[x];i!=-1;i=a[i].nxt){ //当前弧开始(可以不重复访问废边)
        cur[x]=i; //记录当前弧
        int p=a[i].to;
        if(a[i].w && dep[p]==dep[x]+1)
        if((now=dfs(p,min(flow,a[i].w)))){
            a[i].w-=now;
            a[i^1].w+=now;
            ans+=now,flow-=now; //流量更新
            if(flow==0)break;
        }
    }
    return ans;
}
void init(int n){ //初始化
    a.clear();
    fill(head,head+n,-1);
    fill(inque,inque+n,0);
}
int solve(){ //返回最大流
    int ans=0;
    while(bfs())
        ans+=dfs(s,inf);
    return ans;
}
#define add(x,y,w) addedge(x,y,w),addedge(y,x,0)
//赋值n,再init(n),再添边和s,t赋值,最后solve()

ISAP,仅一次BFS与多路增广,\(O(V^2E)\)

struct FLOW{ //ISAP最大流
    struct edge{int to,w,nxt;}; //指向,限流,下一条边
    vector a; int head[N]; //前向星 
    int cur[N]; //当前弧 
    int n,s,t; //点数,源点,汇点
    queue q;
    int dep[N],gap[N]; //gap[x]为等于x的dep[i]的个数
    void ae(int x,int y,int w){
        a.push_back((edge){y,w,head[x]});
        head[x]=a.size()-1;
    }
    bool bfs(){ //记录dep和gap
        fill(dep,dep+n,-1); dep[t]=0;
        fill(gap,gap+n,0); gap[0]=1;
        q.push(t);
        while(!q.empty()){
            int x=q.front(); q.pop();
            for(int i=head[x];i!=-1;i=a[i].nxt){
                int p=a[i].to;
                if(dep[p]!=-1)continue;
                dep[p]=dep[x]+1;
                q.push(p);
                gap[dep[p]]++;
            }
        }
        return dep[s]!=-1;
    }
    int dfs(int x,int fl){ //多路增广
        int now,ans=0;
        if(x==t)return fl;
        for(int i=cur[x];i!=-1;i=a[i].nxt){ //当前弧开始(可以不重复访问废边)
            cur[x]=i; //记录当前弧
            int p=a[i].to;
            if(a[i].w && dep[p]+1==dep[x])
            if((now=dfs(p,min(fl,a[i].w)))){
                a[i].w-=now;
                a[i^1].w+=now;
                ans+=now,fl-=now; //流量更新
                if(fl==0)return ans;
            }
        }
        gap[dep[x]]--;
        if(gap[dep[x]]==0)dep[s]=n;
        dep[x]++;
        gap[dep[x]]++;
        return ans;
    }
    void init(int _n){ //初始化
        n=_n+1;
        a.clear();
        fill(head,head+n,-1);
    }
    int solve(int _s,int _t){ //返回最大流
        s=_s,t=_t;
        int ans=0;
        if(bfs())
        while(dep[s]

最小费用最大流

MCMF,单路增广,\(O(VE^2)\)(其实还有类Dinic算法,有空再补

struct FLOW{ //MCMF费用流
    struct edge{int to,w,cost,nxt;}; //指向,限流,费用,下一条边
    vector a; int head[N]; //前向星 
    int n,s,t,totcost; //点数,源点,汇点,总费用
    deque q;
    bool inque[N]; //在队里的不需要入队
    int dis[N]; //费用
    struct{int to,e;}pre[N]; //路径的前一个点,这条边的位置
    void ae(int x,int y,int w,int cost){
        a.push_back((edge){y,w,cost,head[x]});
        head[x]=a.size()-1;
    }
    bool spfa(){ //已死的算法
        fill(dis,dis+n,inf); dis[s]=0;
        q.assign(1,s);
        while(!q.empty()){
            int x=q.front(); q.pop_front();
            inque[x]=0;
            for(int i=head[x];i!=-1;i=a[i].nxt){
                int p=a[i].to;
                if(dis[p]>dis[x]+a[i].cost && a[i].w){
                    dis[p]=dis[x]+a[i].cost;
                    pre[p]={x,i};
                    if(inque[p]==0){
                        inque[p]=1;
                        if(!q.empty()
                        && dis[q.front()]<=dis[p])
                            q.push_back(p);
                        else q.push_front(p);
                        //松弛,或者直接q.push_back(p);
                    }
                }
            }
        }
        return dis[t]!=inf;
    }
    void init(int _n){ //初始化
        n=_n+1;
        a.clear();
        fill(head,head+n,-1);
        fill(inque,inque+n,0);
    }
    int solve(int _s,int _t){ //返回最大流,费用存totcost里 
        s=_s,t=_t;
        int ans=0;
        totcost=0;
        while(spfa()){
            int fl=inf;
            for(int i=t;i!=s;i=pre[i].to)
                fl=min(fl,a[pre[i].e].w);
            for(int i=t;i!=s;i=pre[i].to){
                a[pre[i].e].w-=fl;
                a[pre[i].e^1].w+=fl;
            }
            totcost+=dis[t]*fl;
            ans+=fl;
        }
        return ans;
    }
}flow;
#define add(x,y,w,cost) flow.ae(x,y,w,cost),flow.ae(y,x,0,-cost)
//先flow.init(n),再add添边,最后flow.solve(s,t)

字符串

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

KMP算法,前缀数组pi[]

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

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<

Z函数,扩展kmp

z[x] 表示原字符串与后 (len-x) 个字符组成的字符串中
满足它们的前k个字符组成的字符串相等的k的最大值
(即最大公共前缀长度)
z[0] 是多少都无所谓
字符串匹配可以构造 s=p+#+t
\(O(n)\)

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]r)
            l=i,r=i+z[i]-1;
    }
}

字典树Trie

时间复杂度即遍历复杂度

struct trie{
    int a[1000001][26],top;
    bool exist[1000001];
    void init(){
        top=1;
        mst(a[0],0);
    }
    int add(){
        mst(a[top],0);
        exist[top]=0;
        return top++;
    }
    void insert(const char *s){
        int p=0;
        for(int i=0;s[i];i++){
            int c=s[i]-'a'; //小写字母 
            if(!a[p][c])a[p][c]=add();
            p=a[p][c];
            //son[p]++; //如果要记录子树大小
        }
        exist[p]=1;
    }
    bool find(const char *s){
        int p=0;
        for(int i=0;s[i];i++){
            int c=s[i]-'a'; //小写字母
            if(!a[p][c])return 0;
            p=a[p][c];
        }
        return exist[p];
    }
}t;

马拉车Manacher

\(O(n)\)

int len[N];
char s[N]; //s两倍内存
int solve(char s[]){
    int n=strlen(s)*2+1;
    repeat_back(i,0,n){
        if(i%2==0)s[i+1]='*';
        else s[i+1]=s[i/2];
    }
    n+=2;
    s[0]='#'; s[n-1]=0;
    len[0]=0;
    int mx=0,id=0,ans=0;
    repeat(i,1,n-1){
        if(imx){
            mx=len[i]+i;
            id=i;
            ans=max(ans,len[i]-1); //回文串长度
        }
    }
    return ans;
}

杂项

位运算巨佬操作

中点向下取整 (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)

离散化

从小到大标号并赋值,\(O(n\log n)\)是个好东西

void disc(int a[],int n){
    vector b(a,a+n);
    sort(b.begin(),b.end());
    b.erase(unique(b.begin(),b.end()),b.end());
    repeat(i,0,n)
        a[i]=lower_bound(b.begin(),b.end(),a[i])-b.begin(); //从0开始编号
}

枚举二进制子集

枚举二进制数m的非空子集

for(int s=m;s;s=(s-1)&m){
    work(s);
}

枚举n个元素的大小为k的二进制子集

int s=(1<>1)|y; //这里有一个位反~
}

质数表

42737, 46411, 50101, 52627, 54577, 191677, 194869, 210407, 221831, 241337, 578603, 625409, 713569, 788813, 862481, 2174729, 2326673, 2688877, 2779417, 3133583, 4489747, 6697841, 6791471, 6878533, 7883129, 9124553, 10415371, 11134633, 12214801, 15589333, 17148757, 17997457, 20278487, 27256133, 28678757, 38206199, 41337119, 47422547, 48543479, 52834961, 76993291, 85852231, 95217823, 108755593, 132972461, 171863609, 173629837, 176939899, 207808351, 227218703, 306112619, 311809637, 322711981, 330806107, 345593317, 345887293, 362838523, 373523729, 394207349, 409580177, 437359931, 483577261, 490845269, 512059357, 534387017, 698987533, 764016151, 906097321, 914067307, 954169327

1572869, 3145739, 6291469, 12582917, 25165843, 50331653 (适合哈希的素数)

19260817 原根15,是某个很好用的质数
1000000007 原根5
998244353 原根3

NTT素数表, \(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*10+c-48;
    return x*tag;
}
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)

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个最大值进行测试

求逆序数 | 归并排序

\(O(n\log n)\)

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=mid || (qa[q])){
            t[s++]=a[q++];
            ans+=mid-p; //统计逆序数
        }
        else
            t[s++]=a[p++];
    }
    for(int i=l;i

最大空矩阵 | 悬线法

求01矩阵中全是0的最大连续子矩阵(面积最大)\(O(nm)\)
此处障碍物是正方形。如果障碍只是一些整点,答案从 \(ab\) 变为 \((a+1)(b+1)\)

int n,m,a[N][N],l[N][N],r[N][N],u[N][N];
int getlm(){
    int ans=0;
    repeat(i,0,n)
    repeat(k,0,m)
        l[i][k]=r[i][k]=u[i][k]=(a[i][k]==0);
    repeat(i,0,n){
        repeat(k,1,m)
        if(a[i][k]==0)
            l[i][k]=l[i][k-1]+1; //可以向左延伸几格
        repeat_back(k,0,m-1)
        if(a[i][k]==0)
            r[i][k]=r[i][k+1]+1; //可以向右延伸几格
        repeat(k,0,m)
        if(a[i][k]==0){
            if(i!=0 && a[i-1][k]==0){
                u[i][k]=u[i-1][k]+1; //可以向上延伸几格
                l[i][k]=min(l[i][k],l[i-1][k]);
                r[i][k]=min(r[i][k],r[i-1][k]); //如果向上延伸u格,lr对应的修改
            }
            ans=max(ans,(l[i][k]+r[i][k]-1)*u[i][k]);
        }
    }
    return ans;
}

精确覆盖 | 舞蹈链

在01矩阵中找到某些行,它们两两不相交,且它们的并等于全集(也可以看作n个集合)
x[],y[] 编号从 \(1\) 开始!\(O(\exp)\),节点数<5000

int n,m;
vector rec; //dance后存所有选中的行的编号
struct DLX{
    #define rep(i,i0,a) for(int i=a[i0];i!=i0;i=a[i])
    int u[N],d[N],l[N],r[N],x[N],y[N]; //x[]行,y[]列,N=10010
    int sz[N],h[N]; //一列几个元素,行首元素
    int top;
    void init(){
        top=m;
        repeat(i,0,m+1){
            sz[i]=0;
            u[i]=d[i]=i;
            l[i]=i-1;
            r[i]=i+1;
        }
        l[0]=m;
        r[m]=0;
        repeat(i,0,n+1)h[i]=-1;
        rec.clear();
    }
    void add(int x0,int y0){ //添加(x0,y0)
        top++;
        sz[y0]++;
        x[top]=x0;
        y[top]=y0;
        u[top]=u[y0];
        d[top]=y0;
        u[d[top]]=d[u[top]]=top;
        if(h[x0]<0)
            h[x0]=l[top]=r[top]=top;
        else{
            l[top]=h[x0];
            r[top]=r[h[x0]];
            l[r[h[x0]]]=top;
            r[h[x0]]=top;
        }
    }
    void remove(int c){ //删除列c中出现1的所有行
        l[r[c]]=l[c];
        r[l[c]]=r[c];
        rep(i,c,d)
        rep(j,i,r){
            u[d[j]]=u[j];
            d[u[j]]=d[j];
            sz[y[j]]--;
        }
    }
    void resume(int c){ //重置列c中出现1的所有行
        rep(i,c,d)
        rep(j,i,r){
            u[d[j]]=d[u[j]]=j;
            sz[y[j]]++;
        }
        l[r[c]]=r[l[c]]=c;
    }
    bool dance(int dep=1){ //返回是否可行
        if(r[0]==0)return 1;
        int c=r[0];
        rep(i,0,r)
        if(sz[c]>sz[i])
            c=i; //选取出现1元素次数最少的一列进行删除(依次删除这一列有1的行)
        remove(c);
        rep(i,c,d){
            rep(j,i,r)
                remove(y[j]);
            if(dance(dep+1)){rec.push_back(x[i]);return 1;}
            rep(j,i,l)
                resume(y[j]);
        }
        resume(c);
        return 0;
    }
}dlx;

高维前缀和

以二维为例,t是维数
法一 \(O(n^t2^t)\)
法二 \(O(n^tt)\)

//<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 m;

模拟退火SA

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

由于退火退不出太令人绝望了(其实是太菜了),建议少用

对拍

#include 
#include 
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文件中,可以直接利用进行调试
        }
    }
}

天坑

我真的真的真的太南了

ll t; 1<

数学

[TOC]

数论基本操作

模乘法 | 龟速乘

//int128版本
ll slow(ll a,ll b,ll mod){return (__int128)a*b%mod;}
//每位运算一次版本
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>21)%mod*(1ll<<21)%mod;
    ll r=a*(b&(1ll<<21)-1)%mod;
    return (l+r)%mod;
}

最大公约数

__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 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;
}

单个乘法逆元

扩欧版

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

快速幂版

ll getinv(ll n,ll mod){ //mod必须必须是质数!!
    return qpow(n,mod-2,mod);
}

唯一分解

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

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]++;
}

set维护版

struct fac{
    #define facN 1010
    ll a[facN]; set s;
    fac(){mst(a,0); s.clear();}
    void lcm(ll n){ //self=lcm(self,n)
        repeat(i,2,facN)
        if(n%i==0){
            int cnt=0;
            while(n%i==0)
                cnt++,n/=i;
            a[i]=max(a[i],cnt); //改成a[i]+=cnt就变成了乘法
        }
        if(n>1)s.insert(n);
    }
    ll value(){ //求自己的模意义下的值
        ll ans=1;
        repeat(i,2,facN)
        if(a[i])
            ans=ans*qpow(i,a[i],mod)%mod;
        for(auto i:s)
            ans=ans*i%mod;
        return ans;
    }
}f;

单个欧拉函数

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

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

素数判定

朴素算法,\(O(\sqrt n)\)

bool isprime(int n){
    if(n<=3)return n>=2;
    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;
}

Miller-Rabin素性测试,\(O(10\cdot\log^3 n)\)

bool isprime(ll n){
    if(n<4)return n>1;
    ll a=n-1,b=0;
    while(a%2==0)a/=2,++b;
    repeat(i,0,10){
        ll 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;
}

大数分解 | Pollard-rho

\(O(n^{\tfrac 1 4})\)

//基于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);
    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\),可行性剪枝

数论分块

\(n=(k-n\%k)(n/k)+(n\%k)(n/k+1)\)
\(\lfloor \dfrac{n}{x}\rfloor=C\)\([x_{\min},x_{\max}]\) 作为一块,其中区间内的任一整数 \(x_0\) 满足 \(x_{\max}=n/(n/x_0)\)

for(int l=l0,r;l<=r0;l=r+1){
    r=min(r0,n/(n/l));
    //c=n/l;
    //len=l-r+1;
}

\(\lceil \dfrac{n}{x}\rceil=C\)\([x_{\min},x_{\max}]\) 作为一块:

for(int l=l0,r;l<=r0;l=r+1){
    r=min(r0,n/(n/l)); if(n%r==0)r=max(r-1,l);
    //c=(n+l-1)/l;
    //len=l-r+1;
}

筛法

定理:求出 \(f(p)\)(p为质数)的复杂度不超过 \(O(\log p)\) 的积性函数可以被线性筛

筛素数

\(O(n)\)

//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的最小质因数
        }
    }
}

筛欧拉函数

线性版,\(O(n)\)

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);
        }
    }
}

不是线性但节省力气和空间版,\(O(n\log\log n)\)

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);
    }
}

筛莫比乌斯函数

\(O(n)\)

void get_mu(int n){
    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*a[j]]=0;
                break;
            }
            mu[i*a[j]]=-mu[i];
        }
    }
}

杜教筛

\(S(n)=\sum_{i=1}^nf(i)\)
由公式
\(\sum_{i=1}^n\sum_{d\mid i}g(d)f(\dfrac i d)=\sum_{i=1}^n g(i)S(\lfloor\dfrac n i \rfloor)\)
右式拿出一项得
\(g(1)S(n)=\sum_{i=1}^n\sum_{d\mid i}g(d)f(\dfrac i d)-\sum_{i=2}^n g(i)S(\lfloor\dfrac n i \rfloor)\)

//目前只有mu的杜教筛
struct DU{
    static const int N=2000010;
    map mp_mu;
    int sum_mu[N],a[N],mu[N];
    bool vis[N];
    ll S_mu(ll x){ //求mu的前缀和
        if(x=N)break;
                vis[i*a[j]]=1;
                if(i%a[j]==0){
                    mu[i*a[j]]=0;
                    break;
                }
                mu[i*a[j]]=-mu[i];
            }
        }
        repeat(i,1,N)
            sum_mu[i]=sum_mu[i-1]+mu[i];
    }
}du;

线性递推

线性递推乘法逆元

求1..(n-1)的逆元,\(O(n)\)

void get_inv(int n){
    inv[1]=1;
    repeat(i,2,n)
        inv[i]=mod-mod/i*inv[mod%i]%mod;
}

求a[1..n]的逆元,离线,\(O(n)\)

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

线性递推组合数

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

struct CC{
    static const int N=100010;
    ll fac[N],inv[N];
    CC(){
        fac[0]=1;
        repeat(i,1,N)fac[i]=fac[i-1]*i%mod;
        inv[N-1]=qpow(fac[N-1],mod-2,mod);
        repeat_back(i,0,N-1)inv[i]=inv[i+1]*(i+1)%mod;
    }
    ll operator()(ll a,ll b){ //a>=b 
        return fac[a]*inv[a-b]%mod*inv[b]%mod;
    }
}C;

线性递推二项式系数

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

高级模运算

线性同余方程组 | 中国剩余定理CRT+extra

//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]不需要两两互质,基于扩欧exgcd和龟速乘slow
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;
}

指标or离散对数 | 大步小步BSGS+extra

\(a^x \equiv b \pmod m\)\(O(\sqrt m)\)

//BSGS,a和mod互质
ll bsgs(ll a,ll b,ll mod){ //a^ans%mod==b
    a%=mod,b%=mod;
    static unordered_map 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;
}

阶与原根

判断是否有原根:若 \(m\) 有原根,则 \(m\) 一定是下列形式:\(2,4,p^a,2p^a\)\(p\) 是奇素数, \(a\) 是正整数)

求所有原根:若 \(g\)\(m\) 的一个原根,则 \(g^s\space(1\le s\le\varphi(m),\gcd(s,\varphi(m))=1)\) 给出了 \(m\) 的所有原根。因此若 \(m\) 有原根,则 \(m\)\(\varphi(\varphi(m))\) 个原根

求一个原根,\(O(n\log\log n)\) 实际远远不到

ll getG(ll n){ //求n最小的原根
    static vector a; a.clear();
    ll t=0,k=n-1;
    repeat(i,2,sqrt(k+1)+1)
    if(k%i==0){
        a.push_back(i); //a存放(n-1)的质因数
        while(k%i==0)k/=i;
    }
    if(k!=1)a.push_back(k);
    repeat(i,2,n){ //枚举答案
        bool f=true;
        for(auto j:a)
        if(qpow(i,(n-1)/j,n)==1){
            f=false;
            break;
        }
        if(f)return i;
    }
}

N次剩余

\(x^a \equiv b \pmod m\) ,基于BSGS、原根

//只求一个
ll residue(ll a,ll b,ll mod){ //ans^a%mod==b
    ll g=getG(mod),c=bsgs(qpow(g,a,mod),b,mod);
    if(c==-1)return -1;
    return qpow(g,c,mod);
}
//求所有N次剩余
vector ans;
void allresidue(ll a,ll b,ll mod){ //ans^a%mod==b
    ll g=getG(mod),c=bsgs(qpow(g,a,mod),b,mod);
    ans.clear();
    if(b==0){ans.push_back(0);return;}
    if(c==-1)return;
    ll now=qpow(g,c,mod);
    ll step=(mod-1)/__gcd(a,mod-1);
    ll ps=qpow(g,step,mod);
    for(ll i=c%step;i

二次剩余

对于奇素数模数 \(p\),存在 \(\frac {p-1} 2\) 个二次剩余 \(\{1^2,2^2,...,(\frac {p-1} 2)^2\}\),和相同数量的二次非剩余
对于奇素数模数 \(p\),如果 \(n^{\frac{p-1}2}\equiv1\pmod{p}\) ,则 \(n\) 是一个二次剩余;如果 \(n^{\frac{p-1}2}\equiv-1\pmod{p}\),则 \(n\) 是一个二次非剩余

对于奇素数模数 \(p\),二次剩余的乘积是二次剩余,二次剩余与非二次剩余乘积为非二次剩余,非二次剩余乘积是二次剩余

费马-欧拉素数定理:\((4n+1)\) 型素数只能用一种方法表示为一个范数(两个完全平方数之和),\((4n+3)\) 型素数不能表示为一个范数

二次互反率:记 \(p^{\frac{q-1}2}\) 的符号为 \((\dfrac p q)\) ,则对奇素数 \(p,q\)\((\dfrac p q)\cdot(\dfrac q p)=(-1)^{\frac{p-1}2\cdot\frac{q-1}2}\)

特殊函数

莫比乌斯反演

\([P]=\begin{cases} 1&命题P为真\\0&命题P为假\end{cases}\)
引理1:\(\lfloor \dfrac{a}{bc}\rfloor=\lfloor \dfrac{\lfloor \frac{a}{b}\rfloor}{c}\rfloor\)
引理2:\(n\) 的因数个数 \(≤\lfloor 2\sqrt n \rfloor\)
数论分块:将 \(\lfloor \dfrac{n}{x}\rfloor=C\)\([x_{\min},x_{\max}]\) 作为一块,其中区间内的任一整数 \(x_0\) 满足 \(x_{\max}=\lfloor\dfrac 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(\dfrac n d)\)
\(\varepsilon\) 为狄利克雷卷积的单位元
莫比乌斯函数性质:\(\mu(n)=\begin{cases} 1&n=1\\0&n含有平方因子\\(-1)^k&k为n的本质不同质因数个数\end{cases}\)
莫比乌斯函数是积性函数
超级重要结论:\(\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(\dfrac n d)\)
(补充)欧拉函数性质:\(\varphi*1=id\)

例题:求模意义下的 \(\sum_{i=1}^n \sum_{j=1}^m \dfrac{i\cdot j}{\gcd(i,j)}\)
\(ans=\sum_{i=1}^n\sum_{j=1}^m\sum_{d|i,d|j,\gcd(\frac i d,\frac j d)=1}\dfrac{i\cdot j}d\)
非常经典的化法:
\(ans=\sum_{d=1}^n d\cdot\sum_{i=1}^{\lfloor\frac nd\rfloor}\sum_{j=1}^{\lfloor\frac md\rfloor}[\gcd(i,j)=1]i\cdot j\)
\(sum(n,m)=\sum_{i=1}^{n}\sum_{j=1}^{m}[\gcd(i,j)=1]i\cdot j\)
\(sum(n,m)=\sum_{i=1}^{n}\sum_{j=1}^{m}\sum_{c|i,c|j}{\mu(c)}\cdot i\cdot j\)
\(i'=\dfrac i c,j'=\dfrac j c\)
\(sum(n,m)=\sum_{c=1}^n\mu(c)\cdot c^2\cdot\sum_{i'=1}^{\lfloor\frac nc\rfloor}\sum_{j'=1}^{\lfloor\frac mc\rfloor} i'\cdot j'\)
易得 \(\sum_{i=1}^{n}\sum_{j=1}^{m} i\cdot j=\dfrac 1 4 n(n+1) m(m+1)\)

斐波那契数列

定义:\(F_0=0,F_1=1,F_n=F_{n-1}+F_{n-2}\)
\(F_n=\dfrac 1 {\sqrt{5}} [(\dfrac 1 \Phi)^n-(-\Phi)^n)]\)
\(F_{a+b-1}=F_{a-1}F_{b-1}+F_aF_b\) (重要公式)
\(F_{n-1}F_{n+1}-F_n^2=(-1)^n\) (卡西尼性质)
\(F_{n}^2+F_{n+1}^2=F_{2n+1}\)
\(F_{n+1}^2-F_{n-1}^2=F_{2n}\) (由上一条写两遍相减得到)
\(F_1+F_3+F_5+...+F_{2n-1}=F_{2n}\) (奇数项求和)
\(F_2+F_4+F_6+...+F_{2n}=F_{2n+1}-1\) (偶数项求和)
\(F_1^2+F_2^2+F_3^2+...+F_n^2=F_nF_{n+1}\)
\(F_1+2F_2+3F_3+...+nF_n=nF_{n+2}-F_{n+3}+2\)
\(-F_1+F_2-F_3+...+(-1)^nF_n=(-1)^n(F_{n+1}-F_n)+1\)
\(F_{2n-2m-2}(F_{2n}+F_{2n+2})=F_{2m+2}+F_{4n-2m}\)
\(F_a \mid F_b \Leftrightarrow a \mid b\)
\(\gcd(F_a,F_b)=F_{\gcd(a,b)}\)
\(p\)\(5k\pm 1\) 型素数时,\(\begin{cases} F_{p-1}\equiv 0\pmod p \\ F_p\equiv 1\pmod p \\ F_{p+1}\equiv 1\pmod p \end{cases}\)
\(p\)\(5k\pm 2\) 型素数时,\(\begin{cases} F_{p-1}\equiv 1\pmod p \\ F_p\equiv -1\pmod p \\ F_{p+1}\equiv 0\pmod p \end{cases}\)
\(F_{n+2}\) 为集合 {1,2,3,...,n-2} 中不包含相邻正整数的子集个数(包括空集)
F(n)%m 的周期 \(\le 6m\)\(m=2\times 5^k\) 取等号)

快速倍增法求\(F_n\),返回二元组\((F_n,F_{n+1})\)\(O(\log n)\)

pii fib(ll n){ //fib(n).first
    if(n==0)return {0,1};
    pii p=fib(n>>1);
    ll a=p.first,b=p.second;
    ll c=a*(2*b-a);
    ll d=a*a+b*b;
    if(n&1)return {d,c+d};
    else return {c,d};
}

排列组合

组合数取模 | 卢卡斯定理Lucas+extra

卢卡斯定理用来求模意义下的组合数

//Lucas,p是质数
ll lucas(ll a,ll b,ll p){
    if(b==0)return 1;
    return slow(c(a%p,b%p,p),lucas(a/p,b/p,p),p);
}
//exLucas,p不需要是质数
//空缺

编码与解码

(多重集康托展开与逆展开)
<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>
给定编号求字符串,对每一位进行尝试即可

康托展开Cantor+逆

排列里的元素都是从1到n

//普通版,O(n^2)
int cantor(int a[],int n){
    int f=1,ans=1; //假设答案最小值是1
    repeat_back(i,0,n){
        int cnt=0;
        repeat(j,i+1,n)
            cnt+=a[j] s;
    x--;
    repeat(i,1,n+1)s.insert(i);
    repeat(i,0,n){
        int q=x/f[n-i-1];
        x%=f[n-i-1];
        auto it=s.begin();
        repeat(i,0,q)it++; //第q+1小的数
        ans[i]=*it;
        s.erase(it);
    }
    return ans;
}

浮点数误差分析

const lf err=1e-11;
if(fabs(x)

博弈论

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 颗石子
两人每次可以拿任意堆任意非空的石子,拿不了的人判负
解:NimSum=XOR{ai},当且仅当 NimSum 为0时先手必败(SG证)

注:先手必胜策略是找到任意第 (63-__builtin_clzll(NimSum)) 位是1的a[i],并取走 a[i]^NimSum 个石子

Nimk

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

多项式

拉格朗日插值

函数曲线通过n个点 \((x_i,y_i)\),求 \(f(k)\)
拉格朗日插值:\(f(x)=\sum_{i=1}^n[y_i\Pi_{j!=i}\dfrac{x-x_j}{x_i-x_j}]\)
\(O(n^2)\)

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

求两个多项式的卷积,\(O(n\log n)\)

struct cf{
    lf x,y;
    cf(lf x=0,lf 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,输出的x改成real()
const lf 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<>1]>>1)|((i&1)<<(bit-1));
        if(i

线性代数

矩阵乘法、矩阵快速幂

struct matrix{
    static const int N=110;
    ll a[N][N];
    matrix(int e=0){
        repeat(i,0,n)
        repeat(j,0,n)
            a[i][j]=e*(i==j);
    }
    matrix operator*(matrix &b)const{ //矩阵乘法
        matrix ans;
        repeat(i,0,n)
        repeat(j,0,n){
            ll &t=ans.a[i][j];
            repeat(k,0,n)
                t=(t+a[i][k]*b.a[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;
}

矩阵高级操作

struct matrix{
    static const int N=110;
    //int n,m;
    lf a[N][N];
    lf det;
    void r_swap(int x,int y){ //交换第x行和第y行
        repeat(i,0,m)
            swap(a[x][i],a[y][i]);
        det=-det;
    }
    void r_mul(int x,lf k){ //第x行乘以实数k
        repeat(i,0,m) //从x开始也没太大关系(对高斯消元来说)
            a[x][i]*=k; //a[x][i]=slow(a[x][i],k);
        det/=k; //det=slow(det,getinv(k));
    }
    void r_plus(int x,int y,lf k){ //第x行加上第y行的k倍
        repeat(i,0,m)
            a[x][i]+=a[y][i]*k; //a[x][i]=(a[x][i]+slow(a[y][i],k))%mod;
    }
    bool gauss(){ //高斯消元,返回是否满秩,注意必须n<=m
        det=1;
        repeat(i,0,n){
            int t=-1;
            repeat(j,i,n)
            if(fabs(a[j][i])>err){ //a[j][i]!=0
                t=j;
                break;
            }
            if(t==-1){det=0; return 0;}
            if(t!=i)r_swap(i,t);
            r_mul(i,1/a[i][i]); //r_mul(i,getinv(a[i][i]));
            repeat(j,0,n) //如果只要det可以从i+1开始
            if(j!=i && fabs(a[j][i])>err) //a[j][i]!=0
                r_plus(j,i,-a[j][i]);
        }
        return 1;
    }
    lf get_det(){ //返回行列式
        gauss();
        return det; //return (det+mod)%mod;
    }
    bool get_inv(){ //把自己变成逆矩阵,返回是否成功
        if(n!=m)return 0;
        repeat(i,0,n)
        repeat(j,0,n)
            a[i][j+n]=i==j; //生成
        m=2*n;
        bool t=gauss();
        m=n;
        repeat(i,0,n)
        repeat(j,0,n)
            a[i][j]=a[i][j+n];
        return t;
    }
};

矩阵的一些结论

\(n\times n\)方阵\(A\)有:\(\left[\begin{array}{c}A&E\\O&E\end{array}\right]^{k+1}=\left[\begin{array}{c}A^k&E+A+A^2+...+A^k\\O&E\end{array}\right]\)

矩阵树定理

无向图矩阵树定理
无向图的最小生成树计数

void matrix::addedge(int x,int y){
    a[x][y]--,a[y][x]--;
    a[x][x]++,a[y][y]++;
}
lf matrix::treecount(){
    //for(auto i:eset)addedge(i.fi,i.se); //加边
    n--,m=n; //a[n-1][n-1]的余子式(选任一节点均可)
    return get_det();
}

有向图矩阵树定理
有向图的根向树形图计数,每条边指向父亲
(叶向树形图,即每条边指向儿子,只要修改一个地方)
如果要求所有根的树形图之和,就求逆的主对角线之和乘以行列式(\(A^*=|A|A^{-1}\)

void matrix::addedge(int x,int y){
    a[x][y]--;
    a[x][x]++; //叶向树形图改成a[y][y]++;
}
ll matrix::treecount(){
    //for(auto i:eset)addedge(i.fi,i.se); //加边
    repeat(i,s,n) //s是根节点
    repeat(j,0,n)
        a[i][j]=a[i+1][j];
    repeat(i,0,n)
    repeat(j,s,n)
        a[i][j]=a[i][j+1];
    n--,m=n; //a[s][s]的余子式
    return get_det();
}

BSET定理:有向欧拉图的欧拉回路总数等于任意根的根向树形图个数乘以 \(\Pi(deg(v)-1)!\)(←阶乘)(\(deg(v)\)\(v\) 的入度或出度,反正入度等于出度

线性基

插入、查询最大值、查询是否存在 \(O(\log M)\),查询第k小 \(O(\log^2 M)\)

struct basis{
    #define n 63
    ll a[n];
    basis(){mst(a,0);}
    void insert(ll x){ //插入元素
        repeat_back(i,0,n)
        if((x>>i)&1)
        if(a[i]==0){
            a[i]=x;
            return;
        }
        else x^=a[i];
    }
    ll top(){ //最大值
        ll ans=0;
        repeat_back(i,0,n)
            ans=max(ans,ans^a[i]);
        return ans;
    }
    bool exist(ll x){ //是否存在
        repeat_back(i,0,n)
        if((x>>i)&1)
        if(a[i]==0){
            return false;
        }
        else x^=a[i];
        return true;
    }
    void rebuild(){ //求第k小的前置操作
        repeat_back(i,0,n)
        repeat_back(j,0,i)
        if((a[i]>>j)&1)
            a[i]^=a[j];
    }
    ll kth(ll k){ //第k小
        ll ans=0;
        rebuild();
        repeat(i,0,n)
        if(a[i]!=0){
            if(k&1==1)ans^=a[i];
            k>>=1;
        }
        return ans;
    }
    #undef n
};

数学杂项

约瑟夫问题

n个人编号0..(n-1),每次数到k出局,求最后剩下的人的编号

线性算法,\(O(n)\)

int jos(int n,int k){
    int res=0;
    repeat(i,1,n+1)res=(res+k)%i;
    return res; //res+1,如果编号从1开始
}

对数算法,适用于k较小情况,\(O(k\log n)\)

int jos(int n,int k){
    if(n==1 || k==1)return n-1;
    if(k>n)return (jos(n-1,k)+k)%n; //线性算法
    int res=jos(n-n/k,k)-n%k;
    if(res<0)res+=n; //mod n
    else res+=res/(k-1); //还原位置
    return res; //res+1,如果编号从1开始
}

格雷码和汉诺塔

格雷码

一些性质:
相邻格雷码只变化一次
grey(n-1)grey(n) 修改了二进制的第 (__builtin_ctzll(n)+1)
grey(0)..grey(2^k-1) 是k维超立方体顶点的哈密顿回路,其中格雷码每一位代表一个维度的坐标

格雷码变换,正 \(O(1)\),逆 \(O(logn)\)

ll grey(ll n){ //第n个格雷码
    return n^(n>>1);
}
ll degrey(ll n){ //逆格雷码变换,法一
    repeat(i,0,63) //or 31
        n=n^(n>>1);
    return n;
}
ll degrey(ll n){ //逆格雷码变换,法二
    int ans=0;
    while(n){
        ans^=n;
        n>>=1;
    }
    return ans;
}

汉诺塔

假设盘数为n,总共需要移动 (1<
第k次移动第 i=__builtin_ctzll(n)+1 小的盘子
该盘是第 (k>>i)+1 次移动
(可以算出其他盘的状态:总共移动了 ((k+(1<<(i-1)))>>i) 次)
该盘的移动顺序是:
A->C->B->A(当i和n奇偶性相同)
A->B->C->A(当i和n奇偶性不同)

cin>>n; //层数
repeat(k,1,(1<>i)%3; //移动前状态
    int p2=(p1+1)%3; //移动后状态
    if(i%2==n%2){
        p1=(3-p1)%3;
        p2=(3-p2)%3;
    }
    cout<<"move "< "<<"ABC"[p2]<

置换群计数

Polya定理
例:立方体 \(n=6\) 个面,每个面染上 \(m=3\) 种颜色中的一种
两个染色方案相同意味着两个立方体经过旋转可以重合
其染色方案数为:\(\dfrac{\sum m^{k_i}}{|k|}\)\(k_i\) 为某一置换可以拆分的循环置换数,\(|k|\) 为所有置换数)

不旋转,{U|D|L|R|F|B},k=6,共1个k
对面中心连线为轴的90度旋转,{U|D|L R F B},k=3,共6个k
对面中心连线为轴的180度旋转,{U|D|L R|F B},k=4,共3个k
对棱中点连线为轴的180度旋转,{U L|D R|F B},k=3,共6个k
对顶点连线为轴的120度旋转,{U L F|D R B},k=2,共8个k

因此 \(\dfrac{3^6+3^3 \cdot 6+3^4 \cdot 3+3^3 \cdot 6+3^2 \cdot 8}{1+6+3+6+8}=57\)

例题(poj1286),n个点连成环,染3种颜色,允许旋转和翻转

ans=0,cnt=0;
//只考虑旋转,不考虑翻转
repeat(i,1,n+1)
    ans+=qpow(3,__gcd(i,n));
cnt+=n;
//考虑翻转
if(n%2==0)ans+=(qpow(3,n/2+1)+qpow(3,n/2))*(n/2);
else ans+=qpow(3,(n+1)/2)*n;
cnt+=n;
cout<

一些简单的数学结论

三个水杯容量为a,b,c(正整数),a=b+c且a装满水,得到容积为a/2的水需要倒a/gcd(b,c)-1次水

兰顿蚂蚁(白色异或右转,黑色异或左转),约一万步后出现周期为104步的无限重复(高速公路)

埃及分数Engel展开:令 \(u_1=x,u_{n+1}=u_n\times\lceil\dfrac 1 {u_n}\rceil-1\)(到0为止)\(a_n=\lceil\dfrac 1 {u_n}\rceil\) \(x=\dfrac 1{a_1}+\dfrac 1{a_1a_2}+\dfrac 1{a_1a_2a_3}+...\)

一个长为n+m的数组,n个1,m个-1,前缀和最大为k,则可能的数组有\(C_{n+m}^{m+k}-C_{n+m}^{m+k+1}\)

若要让点 \(p=xi+yj+zk\) 绕轴 \(l=ai+bj+ck\)(单位向量)转 \(\theta\) 弧度,可以让 \(p\) 左乘 \(q=\cos\dfrac \theta 2+(ai+bj+ck)\sin\dfrac \theta 2\),右乘 \(q^{-1}=\cos\dfrac \theta 2-(ai+bj+ck)\sin\dfrac \theta 2\) (四元数的性质)

任意勾股数能由复数 \((a+bi)^2\space(a,b∈\Z)\) 得到

牛顿迭代:\(x_{n+1}=x_n-\dfrac{f(x)}{f'(x)}\)(求 \(f(x)\) 的零点)

检验 \(x_{n+1}=f(x_n)\) 多次迭代可以收敛于 \(x_0\) 的方法:看 \(|f'(x_0)|\le1\) 是否成立

任意正整数 \(a\) 都存在正整数 \(b,c\) 使得 \(a\(a^2,b^2,c^2\) 成等差数列:构造 \(b=5a,c=7a\)

\(\lim\limits_{n\rightarrow\infty}\dfrac{错排(n)}{n!}=\dfrac 1 e,e\approx 2.718281828459045235360287471352\)

\(\lim\limits_{n\rightarrow\infty}(\sum\frac 1 n-\ln n)=\gamma\approx 0.577215664901532860606\)

任意有理数 \(a\) 可以写成三个有理数的立方和 \(a=(\dfrac{a^3-3^6}{3^2a^2+3^4a+3^6})^3+(\dfrac{-a^3+3^5a+3^6}{3^2a^2+3^4a+3^6})^3+(\dfrac{3^3a^2+3^5a}{3^2a^2+3^4a+3^6})^3\)

四面体体积公式(棱 \(a,b,c\) 共顶点)
\(V^2=\dfrac 1 {288} \left|\left|\begin{array}{c} 0&a^2&b^2&c^2&1\\ a^2&0&c_1^2&b_1^2&1\\ b^2&c_1^2&0&a_1^2&1\\ c^2&b_1^2&a_1^2&0&1\\ 1&1&1&1&0\\ \end{array}\right|\right|\)

拉格朗日四平方和定理:每个正整数都能表示为4个整数平方和
对于偶素数 \(2\)\(2=1^2+1^2+0^2+0^2\)
对于奇素数 \(p\)\(p=a^2+b^2+1^2+0^2\) (容斥可证)
对于所有合数 \(n\)\(n=z_1^2+z_2^2+z_3^2+z_4^2=(x_1^2+x_2^2+x_3^2+x_4^2)\cdot(y_1^2+y_2^2+y_3^2+y_4^2)\)
其中 \(\begin{cases} z_1=x_1y_1+x_2y_2+x_3y_3+x_4y_4 \\ z_2=x_1y_2-x_2y_1-x_3y_4+x_4y_3 \\ z_3=x_1y_3-x_3y_1+x_2y_4-x_4y_2 \\ z_4=x_1y_4-x_4y_1-x_2y_3+x_3y_2\end{cases}\)

Bell数,划分n个元素的集合的方案数

B[0]=B[1]=1;
repeat(i,2,N){
    B[i]=0;
    repeat(j,0,i)
        B[i]=(B[i]+C(i-1,j)*B[j]%mod)%mod;
}

你可能感兴趣的:(ACM模板_axiomofchoice)