目录
- 语法
- 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
的值(2
到53
))
质数表: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