ACM模版集(随缘更新中)

基础板子

#include<bits/stdc++.h>
#define fi first
#define se second
#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)>(b)?(b):(a))
#define ri register
using namespace std;
using ll=long long;
using ull=unsigned long long;
using ld=long double;
ll INF=((ull)~0)>>2;
ll M=1e9+7;
template<typename T>T MO(T x){return (x%M+M)%M;}
void ANS(bool x){cout<<(x?"Yes\n":"No\n");}
std::mt19937 myrand(time(0));
ll rnd(ll l, ll r) {//随机数生成
	return std::uniform_int_distribution<ll>(l, r)(myrand);
}
const int N=200005;
const ld PI=acos(ld(-1));

signed main(){
    cout.tie(0);cin.tie(0)->sync_with_stdio(false);
    //pre();
    //int t;cin>>t;while(t--)
    solve();
    return 0;
}

STL

multiset

好用的东西,可以用来写大顶堆或小顶堆,其可O(logn)查询,删除,插入元素

#include<set>
multiset<ll,greater<ll>> a;//大顶堆
multiset<ll,less<ll>> b;//小顶堆

a.size();//返回集合大小
a.empty();//判断集合是否为空
a.begin();//指向堆顶的迭代器
a.end();//指向正向迭代的最后一个元素下一个位置
a.rbegin();//指向堆底
a.rend();//指向逆向迭代的最后一个元素的下一个元素
a.insert(123);//插入元素,会自动排序
a.find(1234);//返回一个迭代器,指向第一个被查询的元素,如果没有,则指向end()
//a.find()!=a.end()//判断集合内是否存在元素
a.count(143);//统计元素数量
a.erase(it);//按迭代器删除元素,常用搭配:a.erase(a.find(x));//注意得先判断集合内有该元素

算法

并查集

int f[N];
int find(int x){//溯源
	//while(x!=f[x])x=f[x]=f[f[x]];//路径压缩
	//return x;
    return x==f[x]?x:f[x]=find(f[x]);//这样写比while快
}
void join(int u,int v){//连边
	//int a1=find(u),a2=find(v);
	//if(a1!=a2)f[a1]=a2;
    f[find(u)]=find(v);//别管那么多,直接连
}

Kruskal

struct node{//点类
	int u,v,w;
    bool operator<(const &node a){
        return a.w<b.w;
    }
}edge[200005];

int f[5005];
int find(int x){
	while(x!=f[x])x=f[x]=f[f[x]];
	return x;
}//并查集与路径压缩

void kruskal(){
	sort(edge,edge+m);
	int cnt=0;
	for(int i=0;i<m;i++){//多理解一下
		int u=find(edge[i].u),v=find(edge[i].v);
		if(u==v)continue;
		ans+=edge[i].w;
		f[v]=u;
		if(++cnt==n-1)break;
	}
}

极简二分板子(FROM jiangly)

若数列已经按某规则划分成了左右两组,则该STL可快速返回其分界点(右边那组最靠左的元素)。

注意事项:若ck函数在给定范围内均成立,则返回的是r+1的值,如果有必要,这点记得特判一下。

//所需库:algorithm,ranges(C++20才有的,还是得记一下标准二分板子,这个只是偷懒用的)
//返回第一个使ck(x)为假的x
//(l,r+1)因为是左开右闭,所以右端应+1
int ans = *ranges::partition_point(ranges::iota_view(l, r+1),
    [&](int v) {
        return ck(v);
    });

//使用例:
#define PP(r,l,CK) *ranges::partition_point(ranges::iota_view(l, r+1),CK)

ll ans=PP(1,n,[&](int x){
    ...
    return 1;
    ...
    return 0;//ck函数直接写这里就行
});
//注意,如果你使用#define的调用方法,那么CK块里面不能出现逗号
//不然会报错。如果你真的需要在ck函数里写逗号,请auto ck=[&](int x){...};
//再PP(1,n,ck);

最基础的两个线性二分STL:

//返回指向第一个大于v的元素的迭代器
auto mid=upper_bound(a.begin(),a.end(),v);
if(mid==a.end())//此时数组中不存在满足条件的数
else int ans=mid-a.begin();

//返回指向第一个大于等于v的元素的迭代器
auto mid=lower_bound(a.begin(),a.end(),v);

单调队列

struct P{
    ll i,v;
    bool operator<(const P&a)const{
        return v<a.v;//排序方式
    }
}t;
priority_queue<P> q;
FOR(i,1,n){
    cin>>a[i];
}
FOR(i,1,n){
    t.i=i;t.v=a[i];
    q.push(t);/*滑动窗口的条件↓,窗口大小为m*/
    while(!q.empty()&&q.top().i<i-m+1)q.pop();//理解一下
    if(i>=m)cout<<q.top().v<<" ";
}

字符串

Manacher算法

给定一个字符串,该算法可以在 $O(n)$ 的复杂度下处理出该字符串每点的最大回文半径。

vector<int> manacher(string s) {//返回的R数组每项减1才是答案
    string t="#";
    for(auto c:s){t+=c;t+='#';}
    int n=t.size();
    vector<int> r(n);
    for(int i=0,j=0;i<n;i++){
        if(2*j-i>=0&&j+r[j]>i)r[i]=min(r[2*j-i],j+r[j]-i);
        while(i-r[i]>=0&&i+r[i]<n&&t[i-r[i]]==t[i+r[i]])r[i]+=1;
        if(i+r[i]>j+r[j])j=i;
    }
    return r;
}
vector<int> PR;
inline bool PAL(int l,int r){//判回文串(原理:判以(l+r)/2为中心的回文半径是否为l到r的长度)
    return PR[r+l-1]-1==(r-l+1);//为什么要减一,看看上面的马拉车开头部分
    //注意,这里双等号表示所求目标回文串不能是任何其他回文串的中心
    //改成>=的话,就可以略去以上条件
}

字符串单哈希 (来自Hireathsoul,可能有点慢,不知道是不是base取太大了)

给定一个字符串,O(n)预处理,O(1)下判断字符串子串相等的工具

mt19937_64 rnd((ull)time(0));//随机数
ll hshb[]={2663545921,1000000019,1000000033,1000000061,1000000069,1000000097};
struct HASH{//带前导无效字符,下标从1开始
    int n;
    std::vector<ll> hsh,H;
    ll p,base;
    HASH(const std::string &s):n(s.size()),hsh(n),H(n){
        base=hshb[rnd()%6];//随机模数,防止被卡
        p = 1234567891;
        hsh[0] = 1;
        for (int i = 1; i <= n - 1; ++i){
            hsh[i] = hsh[i - 1] * base % p;
            H[i] = (H[i - 1] * base % p + s[i]) % p;
        }
    }
    ll query(int l, int r){
        return (H[r] - H[l - 1] * hsh[r - l + 1] % p + p) % p;
    }
};

字符串双哈希(来自GWTM)

struct Shash{  
    const ll base[2]={29,31};
    const ll hashmod[2]={(ll)1e9+9,998244353};
    array<vector<ll>,2>hsh;
    array<vector<ll>,2>pwMod;
    void init(string S){//传入时不需要加前导空字符,会自动加,下标从1开始
        int n=S.size();S=' '+S;
        hsh[0].resize(n+1),hsh[1].resize(n+1);
        pwMod[0].resize(n+1),pwMod[1].resize(n+1);
        for(int i=0;i<2;++i){
            pwMod[i][0]=1;
            for (int j=1;j<=n;++j){
                pwMod[i][j]=pwMod[i][j-1]*base[i]%hashmod[i];
                hsh[i][j]=(hsh[i][j-1]*base[i]+S[j])%hashmod[i];
            }
        }
    }
    pair<ll,ll>get(int l,int r){
        pair<ll,ll> ans;
        ans.first=(hsh[0][r]-hsh[0][l-1]*pwMod[0][r-l+1])%hashmod[0];
        ans.second=(hsh[1][r]-hsh[1][l-1]*pwMod[1][r-l+1])%hashmod[1];
        ans.first=(ans.first+hashmod[0])%hashmod[0];
        ans.second=(ans.second+hashmod[1])%hashmod[1];
        return ans;
    }
};
bool same(Shash &a,int la,int ra,Shash &b,int lb,int rb){//查询
    return a.get(la,ra)==b.get(lb,rb);
}

zFunction

O(1)查询字符串以各个下标为起始下标时,最大的前缀匹配长度,

auto zFunction(const std::string &s) {
    int n = s.size();
    std::vector<int> z(n);
    for (int i = 1, l = 0, r = 0; i < n; i++) {
        if (i < r)
            z[i] = min(z[i - l], r - i);
        while (i + z[i] < n and s[i + z[i]] == s[z[i]])
            z[i]++;
        if (i + z[i] > r)
            l = i, r = i + z[i];
    }
    z[0]=n;
    return z;
}

数据结构

树状数组

实现单点修改,区间求和

template<typename T>class BIT{
public:
    T tree[N];//开到最大
    inline ll lowbit(ll x){return x&-x;}
    void add(ll x,ll v){//第x个数加上v
        for(ll i=x;i<=n;i+=lowbit(i))tree[i]+=v;
    }
    T sum(ll x){//求前x个数的和
        T ans=0;
        for(ll i=x;i;i-=lowbit(i))ans+=tree[i];
        return ans;
    }
}
//使用方法:
//BIT bit;
//bit.add(i,v);
//bit.sum(i)-bit.sum(j-1);

线段树

区间修改$O(logN)$,区间查询$O(logN)$。

使用举例: 求连续子区间的区间与大于m的最小值。建树,然后1到n枚举左端点,如果 $a[i]<m$ ,那么continue,否则在树上二分其右端点。让ans=min(ans,sum(l,r))

class SGT{
public:
    ll M;ll* tree;ll* tag;
    SGT(int n,ll M_){
        M=M_;
        tree=new ll[(n<<2)+5];
        tag=new ll[(n<<2)+5];
    }
    //线代树所维护的函数
    void modify(int p,int l,int r,ll k){//当符合枚举区间时对区间的操作
            tree[p]=k;
            //常用的还有(记得最后取模):
            //tree[p]+=(r-l+1)*k;//区间加
            //add[p]+=k;//lazytag更新,如果有的话

            //tree[p]*=k;//区间乘
            //mul[p]*=k;

            //tree[p]=(tree[p]*km+(r-l+1)*ka)%M;//区间加和乘(要对lazytag先乘后加)
            //mul[p]*=km;mul[p]%=M;//对乘tag直接乘即可
            //add[p]*=km;add[p]%=M;//对加tag要先乘
            //add[p]+=ka'add[p]%=M;//再加

            //tree[p]=k;//区间替换
            //tag[p]=k;
    }
    void collect(int p){//向上传递子节点的结果,这里用的是区间最大值的模版
        tree[p]=max(tree[p<<1],tree[p<<1|1]);
        //常用的传递还有:
        //tree[p]=tree[p<<1]+tree[p<<1|1];//区间和
        //tree[p]=tree[p<<1]^tree[p<<1|1];//区间异或
    }
    
    ll *base;
    void init(int p,int l,int r){//build建树,可省(?存疑)
        tag[p]=0;//lazytag重置
        if(l==r){tree[p]=base[p];return;}
        int mid=((l+r)>>1);
        init(p<<1,l,mid);
        init(p<<1|1,mid+1,r);
        collect(p);
    }

    int L,R;
    void range(int L_,int R_){L=L_;R=R_;}

    //===================================LZ=====================================//
    void clear(int p,int l,int r){//清空区间lazytag,如果有lazytag就需要这个函数
        int mid=((l+r)>>1);
        modify(p<<1,l,mid,tag[p]);
        modify(p<<1|1,mid+1,r,tag[p]);
        tag[p]=0;//lazytag重置
    }//===================================LZ=====================================//

    void update(int p,int l,int r,ll k){
        if(L<=l&&r<=R){
            modify(int p,int l,int r,ll k);
            return;
        }
        //clear(p,l,r);//如果有lazytag,就得加这句,没有lazytag就不用
        ll mid=((l+r)>>1);
        if(L<=mid)update(p<<1,l,mid,k);
        if(mid<R)update(p<<1|1,mid+1,r,k);
        collect(p);
    }
    ll query(int p,int l,int r){
        if(L<=l&&r<=R)return tree[p];
        int mid=((l+r)>>1);
        ll a=-inf,b=-inf;//这里视情况而定,设为和lazytag一样的默认值
        if(L<=mid)a=query(p<<1,l,mid);
        if(mid<R)b=query(p<<1|1,mid+1,r);
        return max(a,b);//一般是(a+b)%M,(a^b)%M这种
    }
};

//使用例:
//定义线段树类
int main(){
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    CIN(m);CIN(M);ll _=m;

    int cnt=0;ll t=0;
    SGT sgt={200000,M};//以大小和模数声明一个线段树对象
    while(_--){
        char op;cin>>op;
        CIN(x);
        if(op=='A'){
            sgt.range(cnt+1,cnt+1);//设定操作范围
            sgt.update(1,1,m,(x+t)%M);//对设定好的操作范围进行更新
            cnt++;
            //cout<<cnt<<"]";
        }else{
            sgt.range(cnt-x+1,cnt);
            //FOR(i,0,cnt)cout<<sgt.tree[i]<<"]]";
            t=sgt.query(1,1,m);
            cout<<t<<ET;
        }
    }
    return 0;
}

经典线段树:

#include<iostream>
#include<cstdio>
#define ll long long
using namespace std;

#define MAXN 1000001
unsigned ll n,m,a[MAXN],ans[MAXN<<2],tag[MAXN<<2];
inline ll ls(ll x){return x<<1;}
inline ll rs(ll x){return x<<1|1;}
void scan(){
    cin>>n>>m;
    for(ll i=1;i<=n;i++)
    scanf("%lld",&a[i]);
}
inline void push_up(ll p)
{
    ans[p]=ans[ls(p)]+ans[rs(p)];
}
void build(ll p,ll l,ll r)
{
    tag[p]=0;
    if(l==r){ans[p]=a[l];return ;}
    ll mid=(l+r)>>1;
    build(ls(p),l,mid);
    build(rs(p),mid+1,r);
    push_up(p);
} 
inline void f(ll p,ll l,ll r,ll k)
{
    tag[p]=tag[p]+k;
    ans[p]=ans[p]+k*(r-l+1);
}
inline void push_down(ll p,ll l,ll r)
{ 
    ll mid=(l+r)>>1;
    f(ls(p),l,mid,tag[p]);
    f(rs(p),mid+1,r,tag[p]);
    tag[p]=0;
}
inline void update(ll nl,ll nr,ll l,ll r,ll p,ll k)
{
    if(nl<=l&&r<=nr)
    {
        ans[p]+=k*(r-l+1);
        tag[p]+=k;
        return ;
    }
    push_down(p,l,r);
    ll mid=(l+r)>>1;
    if(nl<=mid)update(nl,nr,l,mid,ls(p),k);
    if(nr>mid) update(nl,nr,mid+1,r,rs(p),k);
    push_up(p);
}
ll query(ll q_x,ll q_y,ll l,ll r,ll p)
{
    ll res=0;
    if(q_x<=l&&r<=q_y)return ans[p];
    ll mid=(l+r)>>1;
    push_down(p,l,r);
    if(q_x<=mid)res+=query(q_x,q_y,l,mid,ls(p));
    if(q_y>mid) res+=query(q_x,q_y,mid+1,r,rs(p));
    return res;
}
int main()
{
    ll a1,b,c,d,e,f;
    scan();
    build(1,1,n);
    while(m--)
    {
        scanf("%lld",&a1);
        switch(a1)
        {
            case 1:{
                scanf("%lld%lld%lld",&b,&c,&d);
                update(b,c,1,n,1,d);
                break;
            }
            case 2:{
                scanf("%lld%lld",&e,&f);
                printf("%lld\n",query(e,f,1,n,1));
                break;
            }
        }
    }
    return 0;
}

珂朵莉树

当数据随机时,用于快速处理与 “区间推平” 有关的题。

#define IT set<Node>::iterator
struct Node{//中规中矩的珂朵莉树节点定义
    ll l,r;
    mutable ll v;
    Node(ll l,ll r=0,ll v=0):l(l),r(r),v(v){}
    bool operator<(const Node &a)const{
        return l<a.l;
    }
};

set<Node> s;

IT split(int pos){//珂朵莉树的区间分割
    IT it=s.lower_bound(Node(pos));
    if(it!=s.end()&&it->l==pos){
        return it;
    }
    it--;
    if(it->r<pos)return s.end();
    ll l=it->l;
    ll r=it->r;
    ll v=it->v;
    s.erase(it);
    s.insert(Node(l,pos-1,v));
    return s.insert(Node(pos,r,v)).first;
}

void add(ll l,ll r,ll x){//珂朵莉树的暴力区间加
    IT itr=split(r+1),itl=split(l);//这两句是珂朵莉树经常用到的区间遍历操作
    for(IT it=itl;it!=itr;++it){
        it->v+=x;//这里可以任意更改,修改成其他的区间操作
    }
}

void assign(ll l,ll r,ll x){//珂朵莉树的暴力区间推平
    IT itr=split(r+1),itl=split(l);
    s.erase(itl,itr);
    s.insert(Node(l,r,x));
}

ll kth(ll l,ll r,ll k){//珂朵莉树求区间第k大
    vector<pair<ll,ll>> a;
    auto itr=split(r+1),itl=split(l);//先遍历一遍把区间段全取出来,然后排序
    for(IT it=itl;it!=itr;it++){
        a.push_back({it->v,it->r-it->l+1});
    }
    sort(a.begin(),a.end());

    for(auto it=a.begin();it!=a.end();it++){//再暴力求区间第k大即可
        k-=it->second;
        if(k<=0)return it->first;
    }
    return -1;
}

ll powp(ll x,ll n,ll p){//快速幂,和珂朵莉树无关
    ll ans=1;
    x%=p;
    while(n){
        if(n&1){ans*=x;ans%=p;}
        x*=x;x%=p;n>>=1;
    }
    return ans;
}

ll cal(ll l,ll r,ll x,ll y){//珂朵莉树区间幂求和(其实我个人觉得这个换成更一般的操作也行,思路都是分成几块来管理
    IT itr=split(r+1),itl=split(l);
    ll ans=0;
    for(IT it=itl;it!=itr;it++){
        ans=(ans+powp(it->v,x,y)*(it->r-it->l+1)%y)%y;
    }
    return ans;
}
//======================你可以使用的接口如下:
s.insert(Node(i,i,a[i]));//初始化珂朵莉树,为每个节点赋初始值
add(l,r,x);//区间加
assign(l,r,x);//区间推平
kth(l,r,x);//区间第k大
cal(l,r,x,y);//区间求f(x)和

FHQ Treap

快速求数在数组的前驱,后继,排名(第几大),以及数组中第k大的数。同时可随意添加元素。

const int N=100005;
struct Treap
{
	const int INF;
	int Root,cnt;
	deque<int>del_list;
	struct Node
	{
		int ch[2],v,rnd,siz;
	}node[N];
	int newNode(int x)//申请新节点
	{
		int tmp;
		if(del_list.empty()) tmp=++cnt;
		else tmp=del_list.front(),del_list.pop_front();
		node[tmp].rnd=rand(),node[tmp].v=x,node[tmp].siz=1,node[tmp].ch[0]=node[tmp].ch[1]=0;
		return tmp;
	}
	void update(int x)//更新信息
	{
		node[x].siz=node[node[x].ch[0]].siz+node[node[x].ch[1]].siz+1;
	}
	void vsplit(int pos,int v,int &x,int &y)//按权值分裂
	{
		if(!pos)
		{
			x=y=0;
			return;
		}
		if(node[pos].v<=v) x=pos,vsplit(node[pos].ch[1],v,node[pos].ch[1],y);
		else y=pos,vsplit(node[pos].ch[0],v,x,node[pos].ch[0]);
		update(pos);
	}
	void ssplit(int pos,int k,int &x,int &y)//按size分裂
	{
		if(!pos)
		{
			x=y=0;
			return;
		}
		if(k>node[node[pos].ch[0]].siz)
			x=pos,ssplit(node[pos].ch[1],k-node[node[pos].ch[0]].siz-1,node[pos].ch[1],y);
		else y=pos,ssplit(node[pos].ch[0],k,x,node[pos].ch[0]);
		update(pos);
	}
	int merge(int x,int y)//合并
	{
		if(!x||!y) return x+y;
		if(node[x].rnd<node[y].rnd)
		{
			node[x].ch[1]=merge(node[x].ch[1],y);
			update(x);
			return x;
		}
		node[y].ch[0]=merge(x,node[y].ch[0]);
		update(y);
		return y;
	}
	void insert(int v)//插入
	{
		int x,y;
		vsplit(Root,v,x,y);
		Root=merge(merge(x,newNode(v)),y);
	}
	void erase(int v)//删除
	{
		int x,y,z;
		vsplit(Root,v,x,z);
		vsplit(x,v-1,x,y);
		del_list.push_back(y);
		y=merge(node[y].ch[0],node[y].ch[1]);
		Root=merge(merge(x,y),z);
	}
	int pre(int v)//前驱
	{
		int x,y,cur;
		vsplit(Root,v-1,x,y);
		cur=x;
		while(node[cur].ch[1]) cur=node[cur].ch[1];
		merge(x,y);
		return node[cur].v;
	}
	int nxt(int v)//后继
	{
		int x,y,cur;
		vsplit(Root,v,x,y);
		cur=y;
		while(node[cur].ch[0]) cur=node[cur].ch[0];
		merge(x,y);
		return node[cur].v;
	}
	int get_rank(int v)//查排名
	{
		int x,y,ans;
		vsplit(Root,v-1,x,y);
		ans=node[x].siz;
		merge(x,y);
		return ans;
	}
	int kth(int k)//查排名为k的数
	{
		++k;
		int x,y,cur;
		ssplit(Root,k,x,y);
		cur=x;
		while(node[cur].ch[1]) cur=node[cur].ch[1];
		merge(x,y);
		return node[cur].v;
	}
	Treap():INF(2147483647)//构造函数初始化
	{
		Root=cnt=0;
		insert(-INF),insert(INF);
	}
};
//使用例:
int main(){
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    Treap T;//声明对象
    int n,t,x;
    cin>>n;
    while(n--){
        cin>>t>>x;
        if(t==1)cout<<T.get_rank(x)<<ET;//排名
        else if(t==2)cout<<T.kth(x)<<ET;//第x大的数
        else if(t==3)cout<<T.pre(x)<<ET;//前驱
        else if(t==4)cout<<T.nxt(x)<<ET;//后继
        else T.insert(x);//插入
    }
    return 0;
}

归并排序

排序,求逆序数

template<typename T>class MSORT{
public:
    ll nxs=0;//逆序数
    vector<T> r;//排序结果
    void reset(){
        nxs=0;
    }
    void sort(T* a,int s,int e){
        r.resize(e+1);
        msort(a,s,e);
    }
    void msort(T* a,int s,int e){
        if(s>=e)return;
        int mid=((s+e)>>1);
        msort(a,s,mid);msort(a,mid+1,e);
        int i=s,j=mid+1,k=s;
        while(i<=mid&&j<=e){
            if(a[i]<=a[j]){
                r[k]=a[i];k++;i++;
            }else{
                r[k]=a[j];k++;j++;
                nxs+=mid-i+1;
            }
        }
        while(i<=mid)r[k++]=a[i++];
        while(j<=e)r[k++]=a[j++];
        for(int i=s;i<=e;i++)a[i]=r[i];
    }
};
//使用例:
int a[100000];
...//对a数组完成输入
MSORT<int> S;
S.sort(a,1,n);
cout<<S.nxs<<ET;//输出逆序数
for(int &i:S.r)cout<<i<<" ";//输出排序结果

高精

struct BINT{
    int len=0;
    int w[5000];//最大开到多少位
    BINT(){}
    BINT(ll base){
        while(base){
            w[++len]=base%10;
            base/=10;
        }
    }
    void jw(){//处理进位
        FOR(i,1,len){
            if(w[i]>=10){
                w[i+1]+=w[i]/10;
                w[i]%=10;
                if(w[len+1])len++;
            }
        }
    }
    BINT operator+(BINT b){//高精加法
        FOR(i,1,max(len,b.len)){
            b.w[i]+=w[i];
        }
        b.jw();
        return b;
    }
};
ostream& operator<<(ostream &COUT,BINT a){
    rFOR(i,a.len,1){
        COUT<<a.w[i];
    }
    return COUT;
}

数论

分解质因数

vector<ll> d;
ll tmp=n;
for(ll i=2;i*i<=n;i++){
    if(tmp%i==0){
        d.push_back(i);
        while(tmp%i==0)tmp/=i;
    }
}
if(tmp>1)d.push_back(tmp);//别忘了判一下剩的tmp,通常情况下其不为1

获取约数

vector<int> d(int n){
    vector<int> out;
    FOR(i,1,n){
        if(n%i){
            out.push_back(i);
            if(n/i!=i)out.push_back(n/i);
        }
    }
    return n;
}

容斥定理

非常简单的概念,集合学过吧,$A\bigcup B\bigcup C=A+B+C-(A\bigcap B)-(B\bigcap C)-(A\bigcap C)+(A\bigcap B\bigcap C)$,差不多就是这样的原理。

常用位运算进行集合操作,下面给出的是利用容斥定理求n以内与n不互质的数的个数的算法。$O(2^n)$($n$ 为质因数个数)

ll cnt=d.size();//分解质因数的结果,不含1
ll ans=0;
for(ll i=1;i<(1<<cnt);i++){//
    ll mul=1,num=0;
    FOR(j,0,cnt-1){//
        if(i&(1<<j)){//
            num++;mul*=d[j];
        }
    }
    if(num&1)ans+=n/mul;//
    else ans-=n/mul;//
}

快速幂

ll INF = (((ull)~0)>>2);
ll powp(ll a,ll n,ll M=INF){
    ll ans=1;
    while(n){
        if(n&1){ans*=a;ans%=M;}
        a*=a;a%=M;
        n>>=1;
    }
    return ans;
}

模p运算

$(a+b)mod~p=(a~mod~p+b~mod~p)~mod~p$

$(a-b)mod~p=(a~mod~p-b~mod~p+p)~mod~p$(特别注意要保证结果不为负数

$(ab)mod~p=((a~mod~p)(b~mod~p))mod~p$

$(a/b)mod~p=(a*\textrm{inv}(b))mod~p$ 其中 $\textrm{inv}(b)$ 为 $b$ 在模 $p$ 运算下的乘法逆元。

ll inv(ll x){//利用费马小定理求逆元,注意,模数p不是质数的话就不存在逆元了
    return powp(b,p-2);
}

矩阵快速幂

其实就是照抄快速幂的逻辑,核心还是手写矩阵运算

ll M=10000;
class Matrix{
public:
    int n,m;
    vector<vector<ll> > a;
    Matrix(int n_,int m_){
        n=n_;m=m_;
        a=vector<vector<ll> >(n+1,vector<ll>(m+1,0));
    }
    Matrix(vector<vector<ll> > tmp){//快速创建矩阵(为什么C++98用不了brace-init啊)
        n=tmp.size();m=tmp[0].size();
        a=vector<vector<ll> >(n+1,vector<ll>(m+1,0));
        FOR(i,1,n)FOR(j,1,m)a[i][j]=tmp[i-1][j-1];
    }
    vector<ll>& operator[](ll i){//重载运算符
        return a[i];
    }
    Matrix operator*(Matrix b){//矩阵求积
        Matrix tmp(n,b.m);
        FOR(i,1,n)FOR(j,1,b.m)FOR(k,1,m){
            tmp[i][j]+=a[i][k]*b[k][j];
            tmp[i][j]%=M;
        }
        return tmp;
    }
    Matrix operator^(ll n){//快速幂
        Matrix a=*this,ans=a;ans=1;
        while(n){
            if(n&1)ans=ans*a;
            a=a*a;
            n>>=1;
        }
        return ans;
    }
    void operator=(ll n){//单位矩阵
        FOR(i,1,n)FOR(j,1,m)a[i][j]=(i==j)*n;
    }
    //====接下来是一些和快速幂无关的函数
    Matrix T(){//矩阵求转置
        Matrix tmp(m,n);
        FOR(i,1,n)FOR(j,1,m)tmp[j][i]=a[i][j];
        return tmp;
    }
    void print(){//测试输出用,可以不写
        FOR(i,1,n)FOR(j,1,m)cout<<a[i][j]<<" \n"[j==m];
    }
};
//用法举例:
Matrix a(2,2);
a[1][1]=1;a[1][2]=1;
a[2][1]=1;a[2][2]=0;
cout<<(a^n)[1][2];//求斐波那契第n项(模M)

Matrix b({  {1,2,3},
            {4,5,6} });
(b*(b.T())).print();//矩阵乘法

青春gcd少年不会遇到lcm学姐

求两数间最大公约数 $\gcd(x,y)$

ll gcd(ll a, ll b){
    if(b == 0)return a;
    return gcd(b, a % b);
}
//当然你也可以直接#include<algorithm>,然后用:
__gcd(x,y);

求两数间最小公倍数 $\textrm{lcm}(x,y)$

gcd 与 lcm 的关系如下:

  • $ab=\gcd(a,b)\textrm{lcm}(a,b)$

所以lcm的函数可由gcd函数直接推得:

ll lcm(ll x,ll y){
    return x/__gcd(x,y)*y;//这里先除后乘,防止溢出
}

exgcd,扩展欧几里得

先了解裴蜀(贝祖)定理

裴蜀(贝祖)定理:若 $a,b$ 是整数,且 $\gcd(a,b)=d$,那么对于任意的整数 $x$、$y$,$ax+by$ 都一定是 $d$ 的倍数,特别地,一定存在整数 $x,y$,使 $ax+by=d$ 成立。 同时可知对于一般方程 $ax+by=c$ 有解的条件为 $\color{#ff6666}{c\equiv0~(mod~\gcd(a,b))}$。

拓展gcd即是要求 $ax+by=\gcd(a,b)$ 中 $x$、$y$ 的整数解

设有 $ax+by=\gcd(a,b)$,显然,当 $b = 0$ 时,$\gcd(a,b) = a$,$x = 1$,$y = 任何数$。为了方便这里使 $y = 0$。 当 $b \ne 0$ 时

ll ex_gcd(ll a, ll b, ll &x, ll &y) {
	if(!b) {
		x = 1;
		y = 0;
		return a;
	}
	ll g = ex_gcd(b, a%b, x, y);
	ll t = x;
	x = y;
	y = t - a / b * y;
	return g;//这里返回的是gcd(a,b)
}

组合数预处理

const int MAXN=2e5+5;
ll fac[MAXN],invfac[MAXN];
ll C(ll m,ll n){return (m<0||m>n)?0:MO(MO(fac[n]*invfac[m])*invfac[n-m]);}
ll powp(ll x,ll n){
    ll ans=1;
    while(n){
        if(n&1)ans=MO(ans*x);
        x=MO(x*x);n>>=1;
    }
    return ans;
}
void pre(){
    fac[0]=fac[1]=invfac[0]=invfac[1]=1;
    FOR(i,2,MAXN-1){
        fac[i]=MO(fac[i-1]*i);
        invfac[i]=powp(fac[i],M-2);
    }
}

Catalan数

$Catalan$ 数经常出现在组合计数的问题中,本质上和栈操作有关

下面直接放出 $Catalan$ 数的4个公式:

基本定义式: $$CTL_n=\sum_{i=1}^{n}CTL_i\cdot CTL_{n-i}$$ 递推式: $$CTL_n=\frac{(4n-2)CTL_{n-1}}{n+1}$$ 组合通项式: $$CTL_n=\frac{\textrm{C}{2n}^n}{n+1}$$ 组合通项式2:: $$CTL_n=\textrm{C}{2n}^n-\textrm{C}_{2n}^{n-1}$$

为什么要记这么多式子?当然是推式子化简需要。

其他

优先队列维护前k大的数

这里封成struct了,当然,你直接用主要逻辑也行,不封装也行。

template<typename T> struct BUCKET{
    int k;
    priority_queue<T,vector<T>,greater<T>> q;//前k大(改成less即为前k小)
    BUCKET(int k_){k=k_;}
    void push(T x){
        if(q.size()>=k){
            if(q.top()<x){q.pop();q.push(x);}//前k大(改成>即为前k小)
        }else q.push(x);
    }
    vector<T> get(){
        vector<T> out;
        while(!q.empty()){out.push_back(q.top());q.pop();}
        reverse(out.begin(),out.end());
        return out;
    }
};
//使用方法:
BUCKET<int> buk(k);//创建一个大小为k的桶(我也不知道算不算桶,把它理解成那种往里面加东西,加到桶的容量上限时会溢出来的效果就行)
buk.push(x);//往桶里装东西
auto vec=buk.get();//把桶里的东西倒出来

//特别注意,如果你要用封装的话,T的类型要保证支持
//operator<(T a,T b)
//operator>(T a,T b)
//operator<(const T a,T b)
//operator>(const T a,T b)
//不然会报错

TIPS&WA

这里记录收集到的一些小技巧和注意事项(大部分和STL相关)。

[TIP] 获取一个整数二进制位数

正常做法是写循环位运算,

int cnt=0;
while(x){x>>=1;cnt++;}

但是如果直接用log2函数,写法上会更简洁更快(注意,测了一下,x小于1ll«16时,循环位移运算会快一点)

inline int len(ll x){//获取二进制位数(记得#include<cmath>)
    return x>0?log2l(x)+1:0;
}
//log这里不需要写int,因为返回值自动设为int了

如果你要卡常,试试下面的写法:
inline int len(ll x){
    if(x<=(1ll<<16)){
        int cnt=0;
        while(x){x>>=1;cnt++;}
    }
    return x>0?log2l(x)+1:0;
}

[TIP/WA] 位运算相关

  • 按位左移,正数负数最左边补符号位(除开符号位的最左边);按位右移,正数负数最右边都补0;无符号数当正数处理;

  • 含有对高达$10^{18}$的数进行位运算的题,在对表达式进行左移时记得开ll,比如

    1ll<<n
    
  • 使用~进行按位取反操作,包括符号位。

    可以用~构造INF:

    ll INF=(((ull)~0)>>1);
    
  • 使用__builtin_popcount()获取一个数二进制下1的个数

[TIP/WA] 获取带空格的整行输入

洛谷的字符串题总是喜欢带一堆连续空格,还要求你不能忽略这些空格,这时候就需要用到$$getline(cin,t)$$了。 (什么?不喜欢iostream?那去这里选一个你喜欢的款式:C/C++如何整行输入,我就不一一列举了)

[WA] sort函数与string数组

两个string之间的比较也是要花时间的,若数组大小为 $n$,string长度为 $m$,则sort(a,a+n)的时间复杂的是 $O(nmlogn)$

[TIP/WA] for(auto &i:a)问题

在for(auto &i:a)循环体中对i修改直接反映于原迭代体a,但是不能在这种循环体中往a继续添加元素,不然会报错。

初始化vector数组时可以利用这个特性减少代码量:

vector<int> a(n);
for(auto& i:a)cin>>i;

[TIP] nth_element()与sort()

二者都可以求数组中第 $k$ 大,sort得先对数据进行 $O(n\log n)$ 的排序,再a[k]调用,而nth_element()则只需花 $O(n)$ 的时间进行部分排序,即可使得数组中第 $k$ 大的数就位,且其左边的数都比它小,右边的数都比它大

[TIP] 和python类似的多行字符串输入

使用R"(多行内容)",即可完成,多行字符串的存储。和python的 ‘‘‘多行内容’‘‘差不多。

[WA] 取模问题

有时候可能会遇到一个式子内的取模符号太多,导致很容易丢失重要信息,比如该模的式子没有取模:

cout<<(((pre[n]-(ans>0)*ans)%M+(ans>0)*(ans*powp(2,k)%M)%M)%M+M)%M<<ET;
//某场div2上,有个ans忘记取模了,导致一直WA2,怎么也调不出问题,赛后补上这个%M就AC了:
cout<<(((pre[n]-(ans>0)*ans)%M+(ans>0)*((ans%M)*powp(2,k)%M)%M)%M+M)%M<<ET;

这时候可以考虑开一个函数来处理取模,化繁为简:

inline ll MO(ll x){return (x%M+M)%M;}
cout<<MO(MO(pre[n]-(ans>0)*ans)+(ans>0)*MO(MO(ans)*MO(powp(2,k))))<<ET;
//虽然看着好像差不了多少的样子,但是起码不用再受到%M+M)%M的折磨了

[WA] Python 的高精度问题

有些高精度的题,如果想用python来水的话,要特别注意除法的环节,python在除法时,会把结果转成float,而float型是不支持高精度运算的。所以这时候我们就需要整除。

[WA] map的遍历问题

用for(auto &[key,v]:mp){}即可

但是不知道为什么,上次写了个for(auto &[key1,v1]:mp)for(auto &[key2,v2]:mp)结果TLE了,还以为自己写的不是正解。

破案了,map的下标访问是 $O(logn)$ 的,所以TLE了

[WA] for(ll i=0;i<n;i++)问题

非必要别用ll写for循环,ll的运算比int运算慢了一倍,小心被卡常卡掉。

[WA] lower_bound()问题

这个不是“返回指向第一个小于v的元素的迭代器”的意思,而是“返回指向第一个大于等于v的元素的迭代器”的意思,要想返回第一个小于等于的元素,我建议自己手写一个二分吧。

[WA] 素数筛问题

p数组别开小了,素数只是少,不代表它小,你100000的数据,开2024大小的数组跑素数筛是想WA几次?(Luogu P1621 集合)

[TIP] 处理倍数的一个技巧

使用 (a+k-1)/k*k 可以得到大于等于a的第一个k的倍数。

[WA] 马拉车的R[]查回文子串

菜就多练,别背个板子,抄了还不会用。如果很不太会写怎么验字符串子串是不是回文串,那就直接封装个函数,然后就只需要调这个函数就行了。你问常数?我的建议是,如果你题都没法AC,那怕被卡常而省运行时间又有何用?

vector<int> PR;
inline bool PAL(int l,int r){
    return PR[r+l-1]-1==(r-l+1);
}

(Codeforce Global Round 25 E)

[WA] vector去重

vector去重的步骤是,先sort,再unique,最后erase,别把sort忘了

sort(a.begin(),a.end());
a.erase(unique(a.begin(),a.end()),a.end());

[TIP] 数据输入格式

cin可以读入含前导0的整数,比如说00013的数据,cin读到整数型变量里就变成了13;使用getchar()忽略单个字符

比如说要获取“03:12”的h和m,可以直接这样写:

int h,m;
cin>>h;getchar();cin>>m;

[TIP] 计算几何计算距离的快速写法

正常是这样写:

ld d=sqrtl((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2));

有点麻烦,如果不在意函数的常数的话,可以使用cmath的函数求

ld d=hypot(x1-x2,y1-y2); +

[WA] unordered类型容器

此类容器不能用end和begin来访问,乱写小心CE

[TIP] 降序排序容器

正常sort(a.begin(),a.end())是升序排序容器,如果想要倒序排序,可以用sort(a.rbegin(),a.rend())来排序,而无序写cmp。

[TIP] 判断vector内元素是否全相等

[From jiangly]

if(a==vector<int>(n,a[0]))

[WA] 并查集别用fa数组查根,要用find函数

[TIP] cp的sum函数

//begin(a)=>a.begin()
std::accumulate(begin(a), end(a), 0)
前面的区域以后再来探索吧!