模板:区域赛汇总

版权声明:本文为博主原创文章,欢迎转载并注明来源。 https://blog.csdn.net/w_weilan/article/details/83451549

数据结构

以下数据结构均采用ll作为值类型,应用时根据需求调整。

typedef long long ll;
const ll INF=1e9;//表示(值)正无穷,且两个正无穷相加不会溢出
const int NPOS=-1;//表示(下标)不存在

离散化

在vector基础上的离散化,使用push_back()向其中插值,init()排序并离散化,ask查询离散化之后的值,at/[]运算符查离散前的值。

struct Ranker:vector<ll>
{
	void init()
	{
		sort(begin(),end()),resize(unique(begin(),end())-begin());
	}
	int ask(ll x)const
	{
		return lower_bound(begin(),end(),x)-begin();
	}
};

并查集

struct UnionFindSet:vector<int>
{
	int siz;//剩余连通块数量
	UnionFindSet(int n):siz(n)
	{
		for(int i=0; i<n; ++i)push_back(i);
	}
	int fa(int u)//u所在连通块上根节点
	{
		return at(u)!=u?at(u)=fa(at(u)):u;
	}
	void merge(int u,int w)//归并w所在连通块到u所在连通块
	{
		if(w=fa(w),u=fa(u),w!=u)at(w)=u,--siz;
	}
};

单调队列

typedef pair<int,ll> pil;
struct MonotoneQueue:deque<pil>
{
	void push(pil p,int k)//first插入元素下标,second插入值,插入并维护一个值单调递增的单调队列且队尾队首下标差值小于k
	{
		while(!empty()&&back().second>=p.second)pop_back();
		for(push_back(p); p.first-front().first>=k;)pop_front();
	}
};

ST表

O ( n log n ) O(n\log n) 预处理, O ( 1 ) O(1) 求静态区间最小值。

/*
//可选优化
#define log2(n) LOG2[n]
struct Log:vector<ll>
{
	Log(int N,ll E):vector<ll>(N,-1)
	{
		for(int i=1; i<N; ++i)at(i)=at(i/E)+1;
	}
} LOG2(N,2);
*/
struct SparseTable
{
	vector<vector<ll> > f;
	SparseTable(const vector<ll> &a):f(log2(a.size())+1,a)
	{
		for(int k=0; k+1<f.size(); ++k)
			for(int i=0; i+(1<<k)<a.size(); ++i)
				f[k+1][i]=min(f[k][i],f[k][i+(1<<k)]);
	}
	ll ask(int l,int r)
	{
		int k=log2(r-l+1);
		return min(f[k][l],f[k][r+1-(1<<k)]);
	}
};

树状数组

模板中Base是对应的基础版本,支持单点修改区间查询。

一维

struct Fenwick
{
	struct BaseFenwick
	{
		vector<ll> v;
		BaseFenwick(int last):v(last+1,0) {}
		void add(int x,ll w)
		{
			for(; x<v.size(); x+=x&-x)v[x]+=w;
		}
		ll ask(int x)
		{
			ll ans=0;
			for(; x; x-=x&-x)ans+=v[x];
			return ans;
		}
	};
	pair<BaseFenwick,BaseFenwick> p;
	Fenwick(int last):p(last,last) {}
	void add(int x,ll w)
	{
		p.first.add(x,w),p.second.add(x,x*w);
	}
	void add(int l,int r,ll w)
	{
		add(l,w),add(r+1,-w);
	}
	ll ask(int x)
	{
		return (x+1)*p.first.ask(x)-p.second.ask(x);
	}
	ll ask(int l,int r)
	{
		return ask(r)-ask(l-1);
	}
};

二维

高维的数据结构只要每一维维护低一维的数据即可。其余数据结构亦同理。

struct Fenwick2
{
	struct BaseFenwick2
	{
		vector<Fenwick> v;
		BaseFenwick2(int r,int c):v(r+1,c) {}
		void add(int x,int b,int t,ll w)
		{
			for(; x<v.size(); x+=x&-x)v[x].add(b,t,w);
		}
		ll ask(int x,int b,int t)
		{
			ll ans=0;
			for(; x; x-=x&-x)ans+=v[x].ask(b,t);
			return ans;
		}
	};
	pair<BaseFenwick2,BaseFenwick2> p;
	Fenwick2(int r,int c):p(BaseFenwick2(r,c),BaseFenwick2(r,c)) {}
	void add(int x,int b,int t,ll w)
	{
		p.first.add(x,b,t,w),p.second.add(x,b,t,x*w);
	}
	void add(int l,int b,int r,int t,ll w)//(l,b)~(r,t)
	{
		add(l,b,t,w),add(r+1,b,t,-w);
	}
	ll ask(int x,int b,int t)
	{
		return (x+1)*p.first.ask(x,b,t)-p.second.ask(x,b,t);
	}
	ll ask(int l,int b,int r,int t)
	{
		return ask(r,b,t)-ask(l-1,b,t);
	}
};

线段树

空间优化的线段树,支持区间查询/增加/修改/合并,使用时仅需根据实际情况修改Nodepush_upmaintainask

struct SegmentTree
{
	struct Node
	{
		ll add,set,min,sum;
	};
	vector<Node> v;
	int LAST,L,R;//序列右端点,操作序列左右端点
	SegmentTree(int n):LAST(n),v(2*n+1) {}
	void build(ll a[],int l,int r)//快速建树,调用build(a,1,n)
	{
		if(l<r)
		{
			int m=l+(r-l)/2;
			build(a,l,m),build(a,m+1,r);
			lv(l,r).set=INF,lv(l,r).add=0;//清除本节点标记
		}
		else lv(l,r).set=a[l],lv(l,r).add=0;//两个的和为a[l]即可,根据需要自行选择
		maintain(l,r);
	}
	Node &lv(int l,int r)//[l,r]对应的树上节点
	{
		return v[l+r|l!=r];
	}
	void push_down(Node &lc,Node &rc,Node &fa)//将fa的set、add标记传到lc、rc
	{
		if(fa.set!=INF)
		{
			lc.set=rc.set=fa.set,fa.set=INF;
			lc.add=rc.add=0;
		}
		lc.add+=fa.add;
		rc.add+=fa.add;
		fa.add=0;
	}
	void push_up(const Node &lc,const Node &rc,Node &fa)//将区间左右相连的lc、rc归并到fa
	{
		fa.min=min(lc.min,rc.min);
		fa.sum=lc.sum+rc.sum;
	}
	void maintain(int l,int r)//维护[l,r]
	{
		if(l<r)
		{
			int m=l+(r-l)/2;
			push_up(lv(l,m),lv(m+1,r),lv(l,r));
		}
		if(lv(l,r).set!=INF)
			lv(l,r).sum=(r-l+1)*(lv(l,r).min=lv(l,r).set);
		lv(l,r).min+=lv(l,r).add;
		lv(l,r).sum+=lv(l,r).add*(r-l+1);
	}
	Node ask(int l,int r,ll val=0,bool out=1)//查询[L,R],val为查询路径累加的add标记,out为外部调用标记,下同 
	{
		if(out)return L=l,R=r,ask(1,LAST,val,0);
		if(lv(l,r).set!=INF)
			v[0].sum=(min(R,r)-max(l,L)+1)*(v[0].min=val+lv(l,r).add+lv(l,r).set);
		else if(L<=l&&r<=R)
			v[0].min=lv(l,r).min+val,v[0].sum=lv(l,r).sum+val*(r-l+1);
		else
		{
			int m=l+(r-l)/2;
			if(R<=m)return ask(l,m,lv(l,r).add+val,0);
			if(L>m)return ask(m+1,r,lv(l,r).add+val,0);
			push_up(ask(l,m,lv(l,r).add+val,0),ask(m+1,r,lv(l,r).add+val,0),v[0]);
		}
		return v[0];
	}
	void add(int l,int r,ll val,bool out=1)//[L,R]加上val
	{
		if(out)return L=l,R=r,add(1,LAST,val,0);
		if(L<=l&&r<=R)lv(l,r).add+=val;
		else
		{
			int m=l+(r-l)/2;
			push_down(lv(l,m),lv(m+1,r),lv(l,r));
			if(L<=m)add(l,m,val,0);
			else maintain(l,m);
			if(R>m)add(m+1,r,val,0);
			else maintain(m+1,r);
		}
		maintain(l,r);
	}
	void set(int l,int r,ll val,bool out=1)//[L,R]设为val
	{
		if(out)return L=l,R=r,set(1,LAST,val,0);
		if(L<=l&&r<=R)lv(l,r).set=val,lv(l,r).add=0;
		else
		{
			int m=l+(r-l)/2;
			push_down(lv(l,m),lv(m+1,r),lv(l,r));
			if(L<=m)set(l,m,val,0);
			else maintain(l,m);
			if(R>m)set(m+1,r,val,0);
			else maintain(m+1,r);
		}
		maintain(l,r);
	}
};

主席树

求区间第k小,可能需要离散化。

struct HJTree
{
	struct Node
	{
		int ch[2],sum;
	} v[MAXN<<5];
	int m,siz,tot,T[MAXN];//siz前缀树的个数,tot所有顶点的个数
	void build(int a[],int n,int m)//用a[1]~a[n]建树,a[i]的值不超过m
	{
		for(this->m=m,siz=tot=1; siz<=n; ++siz)
			ins(&(T[siz]=T[siz-1]),a[siz]);
	}
	void ins(int *rt,int pos,int val=1)
	{
		for(int l=1,r=m;;)
		{
			v[tot]=v[*rt],v[*rt=tot++].sum+=val;
			if(l==r)break;
			int m=l+(r-l)/2;
			if(pos>m)rt=&v[*rt].ch[1],l=m+1;
			else rt=&v[*rt].ch[0],r=m;
		}
	}
	int ask(int x,int y,int k)
	{
		for(int kx=T[x-1],ky=T[y],l=1,r=m,LR;;)
		{
			if(l>=r)return l;
			int d=v[v[ky].ch[0]].sum-v[v[kx].ch[0]].sum,m=l+(r-l)/2;
			if(k>d)k-=d,l=m+1,LR=1;
			else r=m,LR=0;
			kx=v[kx].ch[LR],ky=v[ky].ch[LR];
		}
	}
};

动态主席树

struct HJTree
{
	int m,siz,tot,T[MAXN],S[MAXN],use[MAXN];
	struct Node
	{
		int ch[2],sum;
	} v[MAXN<<5];
	void build(int a[],int n,int m)//用a[1]~a[n]建树,a[i]的值不超过m
	{
		for(this->m=m,siz=tot=1; siz<=n; ++siz)
			ins(&(T[siz]=T[siz-1]),a[i]),s[siz]=0;
	}
	void ins(int *rt,int pos,int val=1)//和静态主席树一样
	{
		for(int l=1,r=m;;)
		{
			v[tot]=v[*rt],v[*rt=tot++].sum+=val;
			if(l==r)break;
			int m=l+(r-l)/2;
			if(pos>m)rt=&v[*rt].ch[1],l=m+1;
			else rt=&v[*rt].ch[0],r=m;
		}
	}
	void add(int x,int pos,int val)
	{
		for(; x<siz; x+=x&-x)
			ins(&S[x],pos,val);
	}
	int sum(int x)
	{
		int ret=0;
		for(; x; x-=x&-x)
			ret+=v[v[use[x]].ch[0]].sum;
		return ret;
	}
	int ask(int x,int y,int k)
	{
		for(int i=--x; i; i-=i&-i)use[i]=S[i];
		for(int i=y; i; i-=i&-i)use[i]=S[i];
		for(int kx=T[x],ky=T[y],l=1,r=m,LR;;)
		{
			if(l>=r)return l;
			int m=l+(r-l)/2,d=sum(y)-sum(x)+v[v[ky].ch[0]].sum-v[v[kx].ch[0]].sum;
			if(k>d)k-=d,l=m+1,LR=1;
			else r=m,LR=0;
			kx=v[kx].ch[LR],ky=v[ky].ch[LR];
			for(int i=x; i; i-=i&-i)use[i]=v[use[i]].ch[LR];
			for(int i=y; i; i-=i&-i)use[i]=v[use[i]].ch[LR];
		}
	}
};

无旋Treap

按子树大小分裂

struct FhqTreap
{
	struct Node
	{
		int ch[2],siz,rev;
		ll key,val,min,add;
		void REV()
		{
			rev^=1,swap(ch[0],ch[1]);
		}
		void ADD(ll v)
		{
			val+=v,min+=v,add+=v;
		}
	};
	vector<Node> v;
	int root;
	FhqTreap():v(1),root(0) {}
	void push_down(int k)
	{
		if(!k)return;
		for(int i=0,*ch=v[k].ch; i<2; ++i)
			if(ch[i])
			{
				v[ch[i]].ADD(v[k].add);
				if(v[k].rev)v[ch[i]].REV();
			}
		v[k].add=v[k].rev=0;
	}
	void push_up(int k)
	{
		if(!k)return;
		v[k].siz=1,v[k].min=v[k].val;
		for(int i=0,*ch=v[k].ch; i<2; ++i)
			if(ch[i])
				v[k].siz+=v[ch[i]].siz,v[k].min=min(v[k].min,v[ch[i]].min);
	}
	int merge(int a,int b)
	{
		if(!a||!b)return a+b;
		if(v[a].key<v[b].key)
			return push_down(a),v[a].ch[1]=merge(v[a].ch[1],b),push_up(a),a;
		return push_down(b),v[b].ch[0]=merge(a,v[b].ch[0]),push_up(b),b;
	}
	void split(int a,int s,int &l,int &r)
	{
		if(!s)l=0,r=a;
		else if(v[v[a].ch[0]].siz<s)
			push_down(a),split(v[a].ch[1],s-v[v[a].ch[0]].siz-1,v[a].ch[1],r),push_up(l=a);
		else
			push_down(a),split(v[a].ch[0],s,l,v[a].ch[0]),push_up(r=a);
	}
	void push_back(ll d)
	{
		v.push_back(Node {{0,0},1,0,rand(),d,d,d});
		root=merge(root,v.size()-1);
	}
	void insert(int x,ll d)
	{
		v.push_back(Node {{0,0},1,0,rand(),d,d,d});
		int a,b,c;
		split(root,x-1,a,b);
		root=merge(merge(a,v.size()-1),b);
	}
	void erase(int x)
	{
		int a,b,c;
		split(root,x,a,b),split(a,x-1,a,c),root=merge(a,b);
	}
	void add(int l,int r,ll d)
	{
		int a,b,c;
		split(root,r,b,c),split(b,l-1,a,b),v[b].ADD(d),root=merge(merge(a,b),c);
	}
	Node ask(int l,int r)
	{
		int a,b,c;
		split(root,r,b,c),split(b,l-1,a,b);
		Node ret=v[b];
		return root=merge(merge(a,b),c),ret;
	}
	void reverse(int l,int r)
	{
		int a,b,c;
		split(root,r,b,c),split(b,l-1,a,b),v[b].REV(),root=merge(merge(a,b),c);
	}
	void revolve(int l,int r,int d)
	{
		int a,b,c,e=r-l+1;
		split(root,r,b,c),split(b,l-1,a,b),split(b,(e-d%e)%e,b,e);
		root=merge(merge(a,merge(e,b)),c);
	}
};

按值大小分裂,即排序树

struct FhqTreap
{
	struct Node
	{
		int ch[2],siz;
		ll key,val;
	};
	vector<Node> v;
	int root;
	FhqTreap():v(1),root(0) {}
	void push_up(int k)
	{
		v[k].siz=v[v[k].ch[0]].siz+v[v[k].ch[1]].siz+1;
	}
	int merge(int a,int b)
	{
		if(!a||!b)return a+b;
		if(v[a].key<v[b].key)
			return v[a].ch[1]=merge(v[a].ch[1],b),push_up(a),a;
		return v[b].ch[0]=merge(a,v[b].ch[0]),push_up(b),b;
	}
	void splitVal(int a,ll w,int &l,int &r)//按值将树划分,使得左子树上的值恰小于w
	{
		if(!a)l=r=0;
		else if(v[a].val>w)splitVal(v[a].ch[0],w,l,v[a].ch[0]),push_up(r=a);
		else splitVal(v[a].ch[1],w,v[a].ch[1],r),push_up(l=a);
	}
	void insert(ll x)
	{
		int a,b;
		v.push_back(Node {{0,0},1,rand(),x});
		splitVal(root,x,a,b),root=merge(merge(a,v.size()-1),b);
	}
	void erase(ll x)
	{
		int a,b,c;
		splitVal(root,x,a,b),splitVal(a,x-1,a,c);
		root=merge(merge(a,merge(v[c].ch[0],v[c].ch[1])),b);
	}
	ll kth(int k)
	{
		for(int u=root,ls;;)
		{
			if(ls=v[v[u].ch[0]].siz,ls+1==k)
				return v[u].val;
			if(ls<k)k-=ls+1,u=v[u].ch[1];
			else u=v[u].ch[0];
		}
	}
	int lower_bound(ll x)
	{
		return upper_bound(x-1);
	}
	int upper_bound(ll x)
	{
		int a,b,ret;
		return splitVal(root,x,a,b),ret=v[a].siz+1,root=merge(a,b),ret;
	}
};

珂朵莉树

Willem, Chtholly and Seniorious

区间加减、第k大、k次方和。数据随机。

#include<bits/stdc++.h>
#define mul(a,b,c) ((a)*(b)%(c))
using namespace std;
typedef long long ll;
typedef pair<int,ll> pil;
ll pow(ll a,ll b,ll m)
{
	ll r=1;
	for(a%=m; b; a=mul(a,a,m),b>>=1)
		if(b&1)r=mul(r,a,m);
	return r;
}
struct ChthollyTree:map<int,pil>
{
	iterator split(int pos)
	{
		iterator it=lower_bound(pos);
		if(it!=end()&&it->first==pos)return it;
		--it;
		if(pos>it->second.first)return end();
		pair<int,pil> p=*it;
		erase(it);
		insert(make_pair(p.first,pil(pos-1,p.second.second)));
		return insert(make_pair(pos,p.second)).first;
	}
	void add(int l,int r,ll val)
	{
		for(iterator b=split(l),e=split(r+1); b!=e; ++b)b->second.second+=val;
	}
	void set(int l,int r,ll val)
	{
		erase(split(l),split(r+1)),insert(make_pair(l,pil(r,val)));
	}
	ll rank(int l,int r,int k)
	{
		vector<pair<ll,int> > v;
		for(iterator b=split(l),e=split(r+1); b!=e; ++b)
			v.push_back(make_pair(b->second.second,b->second.first-b->first+1));
		sort(v.begin(),v.end());
		for(int i=0; i<v.size(); ++i)
			if(k-=v[i].second,k<=0)return v[i].first;
		return -1;
	}
	ll sum(int l,int r,ll ex,ll m)
	{
		ll res=0;
		for(iterator b=split(l),e=split(r+1); b!=e; ++b)
			res=(res+mul(b->second.first-b->first+1,pow(b->second.second,ex,m),m))%m;
		return res;
	}
} t;
ll n,m,seed,vmax,M=1e9+7;
ll rnd()
{
	ll ret=seed;
	seed=(seed*7+13)%M;
	return ret;
}
int main()
{
	scanf("%lld%lld%lld%lld",&n,&m,&seed,&vmax);
	for(int i=1; i<=n; ++i)
		t.insert(make_pair(i,pil(i,rnd()%vmax+1)));
	for(int i=1; i<=m; ++i)
	{
		int op=rnd()%4+1,l=rnd()%n+1,r=rnd()%n+1;
		if(l>r)swap(l,r);
		ll x=rnd()%(op==3?r-l+1:vmax)+1;
		if(op==1)t.add(l,r,x);
		if(op==2)t.set(l,r,x);
		if(op==3)printf("%lld\n",t.rank(l,r,x));
		if(op==4)printf("%lld\n",t.sum(l,r,x,rnd()%vmax+1));
	}
}

脑洞治疗仪

把一段连续区间的01移动到到另一段区间上,求在某个区间中最大的连续0序列有多大。

#include<bits/stdc++.h>
using namespace std;
typedef int ll;
typedef pair<int,ll> pil;
struct ChthollyTree:map<int,pil>
{
	iterator split(int pos)
	{
		iterator it=lower_bound(pos);
		if(it!=end()&&it->first==pos)return it;
		--it;
		if(pos>it->second.first)return end();
		pair<int,pil> p=*it;
		erase(it);
		insert(make_pair(p.first,pil(pos-1,p.second.second)));
		return insert(make_pair(pos,p.second)).first;
	}
	void set(int l,int r,ll val)
	{
		erase(split(l),split(r+1)),insert(make_pair(l,pil(r,val)));
	}
	void scure(int l,int r,int x,int y)
	{
		iterator e=split(r+1),b=split(l),it=b;
		int sum=0;
		for(; b!=e; ++b)
			if(b->second.second)
				sum+=b->second.first-b->first+1;
		erase(it,e);
		insert(make_pair(l,pil(r,0)));
		if(!sum)return;
		e=split(y+1),b=split(x),it=b;
		if(sum>=y-x+1)
		{
			erase(b,e);
			insert(make_pair(x,pil(y,1)));
			return;
		}
		for( ; b!=e; ++b)
			if(!b->second.second)
			{
				sum-=b->second.first-b->first+1;
				if(sum<0)return set(b->first,b->second.first+sum,1);
				b->second.second=1;
			}
	}
	ll MAX(int l,int r)
	{
		iterator e=split(r+1),b=split(l);
		ll res=0,now=0;
		for(; b!=e; ++b)
			if(!b->second.second)
				now+=b->second.first-b->first+1;
			else if(now)res=max(res,now),now=0;
		return max(res,now);
	}
} t;
int main()
{
	int n,m;
	scanf("%d%d",&n,&m);
	t.insert(make_pair(1,pil(n,1)));
	for(int op,l,r,x,y; m--;)
	{
		scanf("%d%d%d",&op,&l,&r);
		if(op==0)t.set(l,r,0);
		else if(op==1)scanf("%d%d",&x,&y),t.scure(l,r,x,y);
		else printf("%d\n",t.MAX(l,r));
	}
}

莫队

普通莫队

struct Mo
{
	struct Query
	{
		int l,r,id;
		bool operator<(const Query& n)const
		{
			return l/BS!=n.l/BS?l<n.l:r<n.r;
		}
	};
	vector<Query> q;
	int L,R;
	void query(int l,int r)
	{
		q.push_back(Query {l,r,q.size()});
	}
	void rev(int x) {}
	void cal(int id) {}
	void ask()
	{
		L=0,R=-1;
		sort(q.begin(),q.end());
		for(int i=0; i<q.size(); ++i)
		{
			while(L<q[i].l)rev(L++);
			while(L>q[i].l)rev(--L);
			while(R<q[i].r)rev(++R);
			while(R>q[i].r)rev(R--);
			cal(q[i].id);
		}
	}
};

动态莫队

struct Mo
{
	struct Update
	{
		int pos,NEW,OLD;
	};
	struct Query
	{
		int t,l,r,id;
		bool operator<(const Query& n)const
		{
			return l/BS!=n.l/BS?l<n.l:
			       r/BS!=n.r/BS?r<n.r:t<n.t;
		}
	};
	vector<Update> cq;
	vector<Query> q;
	int T,L,R;
	Mo():cq(1) {}
	void query(int x,int y)
	{
		q.push_back(Query {cq.size()-1,x,y,q.size()});
	}
	void update(int x,int y)
	{
		cq.push_back(Update {x,y,t[x]}),t[x]=y;
	}
	void set(int x,int d)
	{
		if(vis[x])return rev(x),a[x]=d,rev(x);
		a[x]=d;
	}
	void rev(int x) {}
	void cal(int id) {}
	void ask()
	{
		T=L=0,R=-1;
		sort(q.begin(),q.end());
		for(int i=0; i<q.size(); ++i)
		{
			while(T<q[i].t)++T,set(cq[T].pos,cq[T].NEW);
			while(T>q[i].t)set(cq[T].pos,cq[T].OLD),--T;
			while(L<q[i].l)rev(L++);
			while(L>q[i].l)rev(--L);
			while(R<q[i].r)rev(++R);
			while(R>q[i].r)rev(R--);
			cal(q[i].id);
		}
	}
};

树上莫队

按照欧拉序分块,使用Tarjan在生成欧拉序的同时预处理所有询问的lca,预处理时间复杂度 O ( n + q ) O(n+q)
h为查询图,即如果有一个询问(u,v),即在h上连 u v , v u u\to v,v\to u 。多个询问边有序插入h。

struct TreeMo:Graph
{
	struct Query
	{
		int l,r,lca,id;
		bool operator<(const Query &b)const
		{
			return l/BS!=b.l/BS?l<b.l:r<b.r;
		}
	};
	vector<Query> q;
	vector<int> dfp,dfi,dfo;
	UnionFindSet ufs;
	Graph h;
	int L,R;
	TreeMo(int n):Graph(n),h(n),dfp(n*2+1),dfi(n),dfo(n),ufs(n) {}
	void query(int x,int y)
	{
		h.add(Edge {x,y}),h.add(Edge {y,x});
		q.push_back(Query {0,0,0,q.size()});
	}
	void rev(int x) {}
	void cal(int id) {}
	void dfs(int u,int &cnt)
	{
		dfp[dfi[u]=++cnt]=u;
		for(int i=0,k,to; i<v[u].a.size(); ++i)
			if(k=v[u].a[i],to=e[k].to,!dfi[to])
				dfs(to,cnt),ufs.merge(u,to);
		dfp[dfo[u]=++cnt]=u;
		for(int i=0,k,to,id; i<h.v[u].a.size(); ++i)
			if(k=h.v[u].a[i],id=k/2,to=h.e[k].to,dfo[to])
			{
				q[id].lca=ufs.fa(to);
				q[id].l=q[id].lca!=u?dfo[u]:dfi[u];
				q[id].r=dfi[to];
			}
	}
	void ask(int root=1)
	{
		dfs(root,BS=0),BS=sqrt(BS);
		sort(q.begin(),q.end());
		L=0,R=-1;
		for(int i=0; i<q.size(); ++i)
		{
			while(L<q[i].l)rev(dfp[L++]);
			while(L>q[i].l)rev(dfp[--L]);
			while(R<q[i].r)rev(dfp[++R]);
			while(R>q[i].r)rev(dfp[R--]);
			if(q[i].lca!=dfp[L])rev(q[i].lca);
			cal(q[i].id);
			if(q[i].lca!=dfp[L])rev(q[i].lca);
		}
	}
};

动态树上莫队

struct CapitalTreeMo:Graph
{
	struct Update
	{
		int pos,NEW,OLD;
	};
	struct Query
	{
		int t,l,r,lca,id;
		bool operator<(const Query &b)const
		{
			return l/BS!=b.l/BS?l<b.l:
			       r/BS!=b.r/BS?r<b.r:t<b.t;//在BZOJ4129上去掉r/BS还快100ms?
		}
	};
	vector<Update> cq;
	vector<Query> q;
	vector<int> dfp,dfi,dfo;
	UnionFindSet ufs;
	Graph h;
	int T,L,R;
	CapitalTreeMo(int n):cq(1),Graph(n),h(n),dfp(n*2+1),dfi(n),dfo(n),ufs(n) {}
	void query(int x,int y)
	{
		h.add(Edge {x,y}),h.add(Edge {y,x});
		q.push_back(Query {cq.size()-1,0,0,0,q.size()});
	}
	void update(int x,int y)
	{
		cq.push_back(Update {x,y,t[x]}),t[x]=y;
	}
	void dfs(int u,int &cnt)
	{
		dfp[dfi[u]=++cnt]=u;
		for(int i=0,k,to; i<v[u].a.size(); ++i)
			if(k=v[u].a[i],to=e[k].to,!dfi[to])
				dfs(to,cnt),ufs.merge(u,to);
		dfp[dfo[u]=++cnt]=u;
		for(int i=0,k,to,id; i<h.v[u].a.size(); ++i)
			if(k=h.v[u].a[i],id=k/2,to=h.e[k].to,dfo[to])
			{
				q[id].lca=ufs.fa(to);
				q[id].l=q[id].lca!=u?dfo[u]:dfi[u];
				q[id].r=dfi[to];
			}
	}
	void set(int u,int d)
	{
		if(vis[u])return rev(u),a[u]=d,rev(u);
		a[u]=d;
	}
	void rev(int u) {}
	void cal(int id) {}
	void ask(int root=1)
	{
		dfs(root,BS=0),BS=sqrt(BS);
		sort(q.begin(),q.end());
		T=L=0,R=-1;
		for(int i=0; i<q.size(); ++i)
		{
			while(T<q[i].t)++T,set(cq[T].pos,cq[T].NEW);
			while(T>q[i].t)set(cq[T].pos,cq[T].OLD),--T;
			while(L<q[i].l)rev(dfp[L++]);
			while(L>q[i].l)rev(dfp[--L]);
			while(R<q[i].r)rev(dfp[++R]);
			while(R>q[i].r)rev(dfp[R--]);
			if(q[i].lca!=dfp[L])rev(q[i].lca);
			cal(q[i].id);
			if(q[i].lca!=dfp[L])rev(q[i].lca);
		}
	}
};

匹配

KMP

struct KMP
{
    const string s;
    vector<int> next;
    KMP(const string &s):s(s),next(s.size()+1,0)
    {
        for(int i=1,j; i<s.size(); ++i)
        {
            for(j=next[i]; j&&s[i]!=s[j]; j=next[j]);
            next[i+1]=s[i]==s[j]?j+1:0;
        }
    }
    bool find_in(const string &t)
    {
        for(int i=0,j=0; i<t.size(); ++i)
        {
            for(; j&&s[j]!=t[i]; j=next[j]);
            if(s[j]==t[i])++j;
            if(j==s.size())return 1;//不return可得到t中s的所有匹配地址i+1-s.size()
        }
        return 0;
    }
};

AC自动机

ask之前需要调用getFail()生成失配函数。

struct AhoCorasick
{
	struct Node
	{
		int ch[26],val,f,last;
		int &to(char c)
		{
			return ch[c-'a'];
		}//如果不确定c的范围,使用map
	};
	vector<Node> v;
	AhoCorasick():v(1) {}
	void getFail()
	{
		for(deque<int> q(1,v[0].last=v[0].f=0); !q.empty(); q.pop_front())
			for(char c='a'; c<='z'; ++c)
			{
				int r=q.front(),u=v[r].to(c),w=v[r].f;
				if(!r&&u)
				{
					q.push_back(u);
					v[u].f=v[u].last=0;
					continue;
				}
				if(!u)
				{
					v[r].to(c)=v[w].to(c);
					continue;
				}
				q.push_back(u);
				while(w&&!v[w].to(c))w=v[w].f;
				v[u].f=v[w].to(c);
				v[u].last=v[v[u].f].val?v[u].f:
				          v[v[u].f].last;
			}
	}
	void add(const string &s,int val,int u=0)
	{
		for(int i=0; i<s.size(); u=v[u].to(s[i++]))
			if(!v[u].to(s[i]))
			{
				v[u].to(s[i])=v.size();
				v.push_back(Node());
			}
		v[u].val=val;
	}
	bool find_in(const string &s,int u=0)
	{
		for(int i=0; i<s.size(); ++i)
			if(u=v[u].to(s[i]),
			        v[u].val||v[u].last)
				return 1;
		return 0;
	}
};

后缀数组

m:字符集大小。
s:字符串,其中最后一位为加入的0。
sa[i]:字典序第i小的是哪个后缀。
rk[i]:后缀i的排名。
h[i]:lcp(sa[i],sa[i−1])。

struct SufArr
{
	vector<int> sa,rk,h;
	SufArr(const vector<int> &s,int m):sa(s.size(),0),rk(s),h(s.size(),0)
	{
		vector<int> cnt(s.size()+m,0);
		for(int i=0; i<s.size(); ++i)++cnt[rk[i]];
		for(int i=1; i<m; ++i)cnt[i]+=cnt[i-1];
		for(int i=0; i<s.size(); ++i)sa[--cnt[rk[i]]]=i;
		for(int k=1,j=0; k<=s.size()&&j<s.size()-1; k<<=1)
		{
			for(int i=0; i<s.size(); ++i)
			{
				if(j=sa[i]-k,j<0)j+=s.size();
				h[cnt[rk[j]]++]=j;
			}
			cnt[0]=sa[h[0]]=j=0;
			for(int i=1; i<s.size(); ++i)
			{
				if(rk[h[i]]!=rk[h[i-1]]||rk[h[i]+k]!=rk[h[i-1]+k])
					cnt[++j]=i;
				sa[h[i]]=j;
			}
			swap(rk,sa),swap(sa,h);
		}
		for(int i=0,k=0,j=rk[0]; i<s.size()-1; ++i,++k)
			for(; ~k&&s[i]!=s[sa[j-1]+k]; j=rk[sa[j]+1],--k)
				h[j]=k;
	}
};

暴力回文

时间复杂度 O ( n 2 ) O(n^2) ,常数低,但会被ababababa这样的数据卡。

int palindrome(const char *s)
{
	int ans=0;
	for(int i=0,b,e; s[i]; ++i)
	{
		for(b=i; s[i]==s[i+1];)++i;
		for(e=i+1; b&&s[b-1]==s[e];)--b,++e;
		if(ans<e-b)ans=e-b;//此时[b,e)为最大回文区间
	}
	return ans;
}

Manacher线性回文

对于一个位置i,[i−f[i]+1,i+f[i]−1]是最长的以i为中心的奇回文串,g[i]−i是最长的以i为开头的回文串长度。

struct Manacher
{
	vector<int> t,f,g;
	Manacher(const string &s):t(s.size()+1<<1,0),f(t),g(t)//t初始值为s中没有出现过的值,g开始为0
	{
		for(int i=0; i<s.size(); ++i)t[i+1<<1]=s[i];
		for(int i=1,p=0,m=0; i<t.size(); ++i)
		{
			for(f[i]=i<m?min(f[2*p-i],m-i):1;
			        0<i-f[i]&&i+f[i]<t.size()&&
			        t[i-f[i]]==t[i+f[i]];)
				++f[i];
			if(m<i+f[i])m=i+f[p=i];
		}
		for(int i=2; i<t.size(); ++i)
			if(g[i-f[i]+1]<i+1)g[i-f[i]+1]=i+1;
		for(int i=1; i<t.size(); ++i)
			if(g[i]<g[i-1])g[i]=g[i-1];
	}
	int ask(int l,int r)//多次询问可做一个ST表
	{
		int ans=0;
		for(int i=l+1<<1,e=r+1<<1; i<=e; i+=2)
			if(ans<g[i]-i)ans=g[i]-i;
		return ans;
	}
};

图论

这里用类似邻接表的方法存图。有的算法可能需要邻接矩阵,详见模板·线性代数

struct Graph
{
	struct Vertex
	{
		vector<int> a,b;//相关出边和入边编号
		int siz,dep,top,dfn;//树链剖分中使用,依次代表子树节点数、深度、所在链的顶端节点、dfs序
	};
	struct Edge
	{
		int from,to;
		ll dist,cap;//边长、容量,图论算法使用
	};
	vector<Vertex> v;//点集
	vector<Edge> e;//边集
	Graph(int n):v(n) {}
	void add(const Edge &ed)
	{
		if(ed.from==ed.to)return;//如果有需要请拆点
		v[ed.from].a.push_back(e.size());
		v[ed.to].b.push_back(e.size());
		e.push_back(ed);
	}
};

struct Tree:Graph
{
	Tree(int n):Graph(n) {}
	int fa(int k,int i=0)
	{
		return e[v[k].b[i]].from;
	}
	int ch(int k,int i=0)
	{
		return e[v[k].a[i]].to;
	}
	void build(int u,const Graph &g)//无向图dfs建树,且重边在最前,u为根节点
	{
		v[u].siz=1;
		for(int i=0,w,k; i!=g.v[u].a.size(); ++i)
			if(k=g.v[u].a[i],w=g.e[k].to,!v[w].siz)//没访问过的点siz默认0
			{
				build(w,g);
				v[u].siz+=v[w].siz;
				add(g.e[k]);
				if(v[ch(u)].siz<v[w].siz)//重边移到最前
					swap(v[u].a.front(),v[u].a.back());
			}
	}
};

树链剖分与LCA

struct Diagram:Tree
{
	Fenwick data;//暂用树状数组作为默认数据结构
	Diagram(const Graph &g,int root):
		Tree(g.v.size()),data(g.v.size())
	{
		build(root,g);
		int cnt=v[root].dfn=v[root].dep=1;
		dfs(v[root].top=root,cnt);
	}
	void dfs(int u,int &cnt)
	{
		for(int i=0,w; i!=v[u].a.size(); ++i)
		{
			v[w=ch(u,i)].dfn=++cnt;
			v[w].top=i?w:v[u].top;
			v[w].dep=v[u].dep+1;
			dfs(w,cnt);
		}
	}
	int lca(int x,int y)
	{
		for(; v[x].top!=v[y].top; x=fa(v[x].top))
			if(v[v[x].top].dep<v[v[y].top].dep)swap(x,y);
		if(v[x].dep<v[y].dep)swap(x,y);
		return y;
	}
	ll ask(int x,int y)
	{
		ll ans=0;
		for(; v[x].top!=v[y].top; x=fa(v[x].top))
		{
			if(v[v[x].top].dep<v[v[y].top].dep)swap(x,y);
			ans+=data.ask(v[v[x].top].dfn,v[x].dfn);
		}
		if(v[x].dep<v[y].dep)swap(x,y);
		return ans+=data.ask(v[y].dfn,v[x].dfn);
	}
	void add(int x,int y,ll pv)
	{
		for(; v[x].top!=v[y].top; x=fa(v[x].top))
		{
			if(v[v[x].top].dep<v[v[y].top].dep)swap(x,y);
			data.add(v[v[x].top].dfn,v[x].dfn,pv);
		}
		if(v[x].dep<v[y].dep)swap(x,y);
		data.add(v[y].dfn,v[x].dfn,pv);
	}
};

点剖(点分治)

零号点为虚节点。

struct TreeDiv:Graph
{
	int root;
	vector<int> vis,siz,mx;
	TreeDiv(int n):Graph(n),vis(n),siz(n),mx(n,n) {}
	void dfsRoot(int u,int fa)
	{
		for(int i=mx[u]=siz[u]=0,k,to; i<v[u].a.size(); ++i)
			if(k=v[u].a[i],to=e[k].to,to!=fa&&!vis[to])
				if(dfsRoot(to,u),siz[u]+=siz[to],mx[u]<siz[to])
					mx[u]=siz[to];
		if(mx[u]<mx[0]-++siz[u])mx[u]=mx[0]-siz[u];
		if(mx[root]>mx[u])root=u;
	}
	void dfsDist(int u,int fa,ll d)
	{
		//用d更新答案
		for(int i=0,k,to; i<v[u].a.size(); ++i)
			if(k=v[u].a[i],to=e[k].to,to!=fa&&!vis[to])
				dfsDist(to,u,d+e[k].dist);
	}
	int cal(int u,ll d)//返回符合要求的点对数
	{
		return dfsDist(u,0,d),/*得到答案*/;
	}
	void dfs(int u=1)
	{
		dfsRoot(u,root=0),ans+=cal(u=root,0),vis[u]=1;
		for(int i=0,k,to; i<v[u].a.size(); ++i)
			if(k=v[u].a[i],to=e[k].to,!vis[to])
				ans-=cal(to,e[k].dist),mx[0]=siz[to],dfs(to);
	}
};

最小生成树

无向图

同时给出Prim算法(生成新树)、Kruskal算法(消耗小)。

struct Prim:Tree
{
	struct DistGreater
	{
		bool operator()(const Edge &e1,const Edge &e2)
		{
			return e1.dist>e2.dist;
		}
	};
	ll ans;
	vector<int> vis;
	priority_queue<Edge,vector<Edge>,DistGreater> q;
	Prim(const Graph &g,int root):Tree(n),ans(0),vis(g.v.size(),0)//生成新树,每条边都要有等长反向边
	{
		for(insert(root,g); !q.empty();)
		{
			Edge ed=q.top();
			if(q.pop(),!vis[ed.to])
			{
				insert(ed.to,g);
				ans+=ed.dist;
				add(ed);
			}
		}
	}
	void insert(int u,const Graph &g)//把点和对应的相连的边加入集合
	{
		vis[u]=1;
		for(int i=0,k; i!=g.v[u].a.size(); ++i)
			if(k=g.v[u].a[i],!vis[g.e[k].to])
				q.push(g.e[k]);
	}
};
ll kruskal(vector<Edge> &e,int n)//会清空边集e,每条边被认作无向边
{
	ll ret=0;
	UnionFindSet ufs(n);
	for(sort(e.begin(),e.end(),DistGreater()); !e.empty(); e.pop_back())
		if(ufs.fa(e.back().from)!=ufs.fa(e.back().to))
		{
			ufs.merge(e.back().from,e.back().to);
			ret+=e.back().dist;
		}
	return /*ufs.siz>1?INF:*/ret;//视情况选择去注释
}

有向图

指定以root为根,如果没有限定根那么新建一个虚拟点作为根,向所有边连边长最大边长+1的边,在最后生成的图中去掉此边。时间复杂度 O ( V E ) O(VE)

ll zhuLiu(vector<Edge> &e,int root,int n)//不存在返回INF
{
	for(ll ret=0;;)
	{
		vector<ll> in(n,INF);
		vector<int> pre(n,NPOS);
		for(int i=0,to; i<e.size(); ++i)
		{
			if(e[i].from==(to=e[i].to))
				swap(e[i--],e.back()),e.pop_back();
			else if(in[to]>e[i].dist)
				in[to]=e[i].dist,pre[to]=e[i].from;
		}
		for(int i=in[root]=0; i<n; ++i)
			if(in[i]==INF)return INF;
		vector<int> id(n,NPOS),vis(n,NPOS);
		int tn=0;
		for(int i=0,v; i<n; ++i)
		{
			for(ret+=in[v=i]; vis[v]!=i&&id[v]==NPOS&&v!=root; v=pre[v])
				vis[v]=i;
			if(v!=root&&id[v]==NPOS)
			{
				for(int u=pre[v]; u!=v; u=pre[u])
					id[u]=tn;
				id[v]=tn++;
			}
		}
		if(!tn)return ret;
		for(int i=0; i<n; ++i)
			if(id[i]==NPOS)id[i]=tn++;
		for(int i=0,v; i<e.size(); ++i)
			if((e[i].from=id[e[i].from])!=(e[i].to=id[v=e[i].to]))
				e[i].dist-=in[v];
		n=tn,root=id[root];
	}
}

连通性

无向图求割和双连通分量

割边:在连通图中,删除了连通图的某条边后,图不再连通。这样的边被称为割边,也叫做桥。
割点:在连通图中,删除了连通图的某个点以及与这个点相连的边后,图不再连通。这样的点被称为割点。
构造dfs搜索树,在树上有两类节点可以成为割点:
对根节点u,若其有两棵或两棵以上的子树,则该根结点u为割点;
对非根非叶节点u,若其中的某棵子树的节点均没有指向u的祖先节点的回边,说明删除u之后,根结点与该棵子树的节点不再连通;则节点u为割点。
对于一个无向图的子图,当删除其中任意一条边后,不改变图内点的连通性,这样的子图叫做边的双连通子图。而当子图的边数达到最大时,叫做边的双连通分量。原理是图中所有割边再求一次SCC,可直接使用下面求SCC的代码。
对于一个无向图的子图,当删除其中任意一个点后,不改变图内点的连通性,这样的子图叫做点的双连通子图。而当子图的边数达到最大时,叫做点的双连通分量。下面给出求点双连通分量的代码。

struct BCC:Graph//Biconnected Connected Componenet
{
	vector<int> low,bid,stak,cutPoint,cutEdge;//连通块最早dfs序,边的端点所属双连通块
	int bcc_siz;
	BCC(int n):Graph(n) {}
	void ask()
	{
		low.assign(v.size(),NPOS);
		bid.assign(e.size(),NPOS);
		cutPoint.assign(v.size(),0);
		cutEdge.assign(e.size(),0);
		for(int i=bcc_siz=0,cnt=0; i<v.size(); ++i)
			if(low[i]==NPOS)
				dfs(i,NPOS,cnt);
	}
	void dfs(int u,int fa,int &cnt)
	{
		low[u]=v[u].dfn=++cnt;
		for(int i=0,k,to,ch=0; i<v[u].a.size(); ++i)
			if(k=v[u].a[i],to=e[k].to,to!=fa)
			{
				if(low[to]==NPOS)
				{
					++ch;
					stak.push_back(k);
					dfs(to,u,cnt);
					low[u]=min(low[u],low[to]);
					if(low[to]>=v[u].dfn)
						for(++bcc_siz,cutPoint[u]=fa!=NPOS||ch>1;;)
						{
							int x=stak.back();
							stak.pop_back();
							bid[x]=bid[x^1]=bcc_siz-1;
							if(x==k)break;
						}
					if(low[to]>v[u].dfn)cutEdge[k]=cutEdge[k^1]=1;
				}
				else if(v[to].dfn<v[u].dfn)
				{
					stak.push_back(k);
					low[u]=min(low[u],v[to].dfn);
				}
			}
	}
};

双连通图的构造

先求出所有的桥,然后删除这些桥边,剩下的每个连通块都是一个双连通子图。把每个双连通子图收缩为一个顶点,再把桥边加回来,最后的这个图一定是一棵树,边连通度为1。统计出树中度为1的节点的个数,即为叶节点的个数,记为leaf。至少在树上添加(leaf+1)/2条边,就能使树达到边双连通:先把两个最近公共祖先最远的两个叶节点之间连接一条边,这样可以把这两个点到祖先的路径上所有点收缩到一起,因为一个形成的环一定是双连通的;然后再找两个最近公共祖先最远的两个叶节点,这样一对一对找完,恰好是(leaf+1)/2次,把所有点收缩到了一起。

有向图求强连通分量

如果是无向图,求出来的还是边双连通分量。

struct SCC:Graph//Strongly Connected Componenet
{
	vector<int> low,sid,stak;//连通块最早dfs序,点所属连通块
	int scc_siz;
	SCC(int n):Graph(n) {}
	void ask()
	{
		low.assign(v.size(),NPOS);
		sid.assign(v.size(),NPOS);
		for(int i=scc_siz=0,cnt=0; i!=v.size(); ++i)
			if(low[i]==NPOS)
				dfs(i,NPOS,cnt);
	}
	void dfs(int u,int fa,int &cnt)
	{
		low[u]=v[u].dfn=++cnt;
		stak.push_back(u);
		for(int i=0,k,to; i!=v[u].a.size(); ++i)
			if(k=v[u].a[i],to=e[k].to,to!=fa,1)//求边双连通分量把",1"注释掉,即不许走回边
			{
				if(low[to]==NPOS)
					dfs(to,u,cnt),low[u]=min(low[u],low[to]);
				else if(sid[to]==NPOS)
					low[u]=min(low[u],v[to].dfn);
			}
		if(low[u]==v[u].dfn)
			for(++scc_siz;;)
			{
				int x=stak.back();
				stak.pop_back();
				sid[x]=scc_siz-1;
				if(x==u)break;
			}
	}
};

2-SAT

n个布尔变量 x 0 x n 1 x_0\ldots x_{n-1} ,逻辑表达式 Y = ( A 0 + B 0 ) ( A 1 + B 1 ) ( A m 1 + B m 1 ) Y=(A_0+B_0)(A_1+B_1)\ldots(A_{m-1}+B_{m-1}) ,其中 A i , B i { x j , x j } A_i,B_i\in\{x_j,\overline{x_j}\} ,判断是否存在 x 0 x n 1 x_0\ldots x_{n-1} 的取值使得Y值为1。因为 A + B = ( A B ) ( B A ) A+B=(\overline A\to B)(\overline B\to A) ,所以对于一个要求 A + B A+B ,我们连 A B , B A \overline A\to B,\overline B\to A 两条边。如果有一条边 A B A\to B ,意味着如果A成立那么B必然成立。若 i , x i , x i \exists i,x_i,\overline{x_i}\in 同一SCC,则不存在。

最短路

Floyed多源最短路&传递闭包

struct Floyed:Matrix
{
	void ask()//不连通置INF
	{
		for(int k=0; k<n; ++k)
			for(int i=0; i<n; ++i)
				for(int j=0; j<n; ++j)
					if(a[i][j]>a[i][k]+a[k][j])
						a[i][j]=a[i][k]+a[k][j];
	}
};

Dijkstra算法

适用于边权为正的情况(无论有向图还是无向图),用于求单源最短路。
直接给出其优先队列优化的版本。另外,由于priority_queue并不提供修改优先级的操作,为避免重复扩展,这里选择将新元素直接插入队列并在运行时判断该点是否被处理,并不影响结果的正确性。

struct Dijkstra:Graph
{
	vector<ll> d;
	vector<int> p;
	Dijkstra(int n):Graph(n) {}
	void ask(int s)
	{
		d.assign(v.size(),INF);
		p.assign(v.size(),NPOS);
		priority_queue<pair<ll,int> > q;
		for(q.push(make_pair(d[s]=0,s)); !q.empty();)
		{
			ll dis=-q.top().first;
			int u=q.top().second;
			if(q.pop(),d[u]<dis)continue;
			for(int i=0,k,to; i!=v[u].a.size(); ++i)
				if(k=v[u].a[i],to=e[k].to,
				        d[to]>d[u]+e[k].dist)
				{
					d[to]=d[u]+e[k].dist,p[to]=k;
					q.push(make_pair(-d[to],to));
				}
		}
	}
};

BellmanFord算法

直接给出其队列优化、国内称之为SPFA算法的版本。较之Dijkstra算法,此算法不够快速稳定但是可以允许负边存在,当s到达负权回路时会直接返回0。

struct BellmanFord:Graph
{
	vector<ll> d;
	vector<int> p;
	BellmanFord(int n):Graph(n) {}
	bool ask(int s)
	{
		d.assign(v.size(),INF);
		p.assign(v.size(),NPOS);
		vector<int> cnt(v.size(),0),flag(v.size(),d[s]=0);
		for(deque<int> q(cnt[s]=flag[s]=1,s); !q.empty(); q.pop_front())
			for(int u=q.front(),i=flag[u]=0,k,to; i!=v[u].a.size(); ++i)
				if(k=v[u].a[i],to=e[k].to,
				        d[to]>d[u]+e[k].dist)
				{
					d[to]=d[u]+e[k].dist,p[to]=k;
					if(!flag[to])
					{
						if(v.size()==++cnt[to])return 0;
						flag[to]=1,q.push_back(to);
					}
				}
		return 1;
	}
};

差分约束系统

按如下方式建图、跑SPFA:
对每个不等式 x i x j d x_i−x_j\leq d ,从 j j i i 连一条边,边长为 d d
若不等号的方向相反,即 x i x j d x_i−x_j\geq d ,则在不等式两边同时乘以 1 -1 ,变成 x j x i d x_j−x_i\leq -d ,即从 i i j j 连一条边,边长为 d d

Astar求k短路

朴素的想法是使用priority_queue从原点出发向外探索,当取出终点t第k次时就得到第k短路,类似bfs的思想,缺陷是越往后状态数越多。
我们在反向图上从 t s t\to s 跑Astar算法,通过优先展开到s近的状态,使搜索方向靠近答案,而不是一层一层全都展开,估价函数 f g + h f\approx g+h ,f是估计的s到t的距离,g是到达当前点已经点的花费,h是预计剩下的花费。这里h取当前点的距离到s距离,可通过从s跑一遍Dijkstra可以预处理得出。
Astar算法是只有到达终点的时候才能统计答案,这导致可能拓展很多个状态才能得到一个用来更新答案的有效状态。例如一个n元环,当我们到达终点之后,可能还要拓展n次才能得到下一个状态,于是时间复杂度就被卡到 O ( n k ) O(nk)
k下标从0开始,即最短路==第0短路有的坑题需要在调用前补一句if(s==t)++k;

struct Astar:Dijkstra
{
	vector<ll> ans;
	Astar(int n):Dijkstra(n) {}
	void ask(int s,int t,int k)
	{
		Dijkstra::ask(s);
		ans.assign(k,INF);
		if(d[t]==INF)return;
		vector<int> cnt(v.size(),0);
		priority_queue<pair<ll,int> > q;
		for(q.push(make_pair(-d[t],t)); cnt[s]<k&&!q.empty();)
		{
			ll dis=-q.top().first;
			int u=q.top().second;
			if(u==s)ans[cnt[s]]=dis;
			if(q.pop(),++cnt[u]>k)continue;
			for(int i=0,k; i<v[u].b.size(); ++i)
				k=v[u].b[i],q.push(make_pair(d[u]-d[e[k].from]-e[k].dist-dis,e[k].from));
		}
	}
};

可持久化堆求k短路

比Astar相比,有稳定时间复杂度 O ( ( N + M ) log N + M log M + K log K ) O((N+M)\log N+M\log M+K\log K)

//待补充

网络流

ISAP求最大流

struct ISAP:Graph
{
	ll flow;
	vector<ll> f;
	vector<int> h,cur,gap;
	ISAP(int n):Graph(n) {}
	void add(Edge ed)
	{
		Graph::add(ed);
		swap(ed.from,ed.to),ed.cap=0;
		Graph::add(ed);
	}
	ll dfs(int s,int u,int t,ll r)
	{
		if(r==0||u==t)return r;
		ll _f,_r=0;
		for(int &i=cur[u],k; i<v[u].a.size(); ++i)
			if(k=v[u].a[i],h[u]==h[e[k].to]+1)
			{
				_f=dfs(s,e[k].to,t,min(r-_r,e[k].cap-f[k]));
				f[k]+=_f,f[k^1]-=_f,_r+=_f;
				if(_r==r||h[s]>=v.size())return _r;
			}
		if(!--gap[h[u]])h[s]=v.size();
		return ++gap[++h[u]],cur[u]=0,_r;
	}
	void ask(int s,int t)
	{
		h.assign(v.size(),0);
		cur.assign(v.size(),0);
		gap.assign(v.size()+2,0);
		/*for(deque<int> q(h[t]=gap[t]=1,t); !q.empty(); q.pop_front())//可选预处理
			for(int i=0,u=q.front(),k,to; i<v[u].a.size(); ++i)
				if(to=e[v[u].a[i]].to,!h[to])
					++gap[h[to]=h[u]+1],q.push_back(to);*/
		for(f.assign(e.size(),flow=0); h[s]<v.size();)
			flow+=dfs(s,s,t,INF);
	}
};

ZKW求费用流

定义一条边的费用为流量*边长,求总费用最小的最大流。
对于最终流量较大,而费用取值范围不大的图,或者是增广路径比较短的图(如二分图),zkw算法都会比较快,原因是充分发挥优势。比如流多说明可以同一费用反复增广,费用窄说明不用改太多距离标号就会有新增广路,增广路径短可以显著改善最坏情况,因为即使每次就只增加一条边也可以很快凑成最短路。如果恰恰相反,流量不大,费用不小,增广路还较长,就不适合 zkw 算法了。

struct ZKW:Graph
{
	ll flow,cost;
	vector<ll> h,f;
	vector<int> vis;
	ZKW(int n):Graph(n) {}
	void add(Edge ed)
	{
		Graph::add(ed);
		swap(ed.from,ed.to),ed.cap=0,ed.dist*=-1;
		Graph::add(ed);
	}
	ll dfs(int u,int t,ll r)
	{
		if(r==0||u==t)return r;
		if(vis[u])return 0;
		ll _f=vis[u]=1,_r=0;
		for(int i=0,k; r>_r&&i<v[u].a.size(); ++i)
			if(k=v[u].a[i],h[e[k].to]+e[k].dist==h[u])
				_f=dfs(e[k].to,t,min(r-_r,e[k].cap-f[k])),f[k]+=_f,f[k^1]-=_f,_r+=_f;
		return _r;
	}
	void ask(int s,int t)
	{
		h.assign(v.size(),0);
		vis.assign(v.size(),0);
		for(f.assign(e.size(),flow=cost=0);;)
		{
			ll _f=dfs(s,t,INF),d=INF;
			flow+=_f,cost+=_f*h[s];
			for(int u=0; u<v.size(); ++u)
				for(int i=0,k; vis[u]&&i<v[u].a.size(); ++i)
					if(k=v[u].a[i],!vis[e[k].to]&&e[k].cap>f[k])
						d=min(d,e[k].dist+h[e[k].to]-h[e[k].from]);
			if(d==INF)return;
			for(int i=0; i<v.size(); ++i)if(vis[i])h[i]+=d,vis[i]=0;
		}
	}
};

EK算法求费用流

BellmanFord算法找增广路,可能被卡但是可以跑负费用流(最大费用流)。

struct EdmondKarp:Graph
{
	ll flow,cost;
	vector<ll> f;
	EdmondKarp(int n):Graph(n) {}
	void add(Edge ed)
	{
		Graph::add(ed);
		swap(ed.from,ed.to),ed.cap=0,ed.dist*=-1;
		Graph::add(ed);
	}
	void ask(int s,int t)
	{
		vector<int> p(v.size(),NPOS);
		for(f.assign(e.size(),flow=cost=0);;)
		{
			vector<ll> d(v.size(),INF);
			vector<int> flag(v.size(),d[s]=0);
			for(deque<int> q(flag[s]=1,s); !q.empty(); q.pop_front())
				for(int u=q.front(),i=flag[u]=0,k,to; i<v[u].a.size(); ++i)
					if(k=v[u].a[i],to=e[k].to,
					        e[k].cap>f[k]&&d[to]>d[u]+e[k].dist)
					{
						d[to]=d[u]+e[k].dist,p[to]=k;
						if(!flag[to])q.push_back(to),flag[to]=1;
					}
			if(d[t]==INF)return;
			ll _f=INF;
			for(int u=t; u!=s; u=e[p[u]].from)
				_f=min(_f,e[p[u]].cap-f[p[u]]);
			for(int u=t; u!=s; u=e[p[u]].from)
				cost+=_f*e[p[u]].dist,f[p[u]]+=_f,f[p[u]^1]-=_f;
			flow+=_f;
		}
	}
};

PD算法求费用流

性能优秀,只能跑非负权图。

struct PrimalDual:Graph
{
	ll flow,cost;
	vector<ll> f;
	PrimalDual(int n):Graph(n) {}
	void add(Edge ed)
	{
		Graph::add(ed);
		swap(ed.from,ed.to),ed.cap=0,ed.dist*=-1;
		Graph::add(ed);
	}
	void ask(int s,int t)//询问s到t的最小费用最大流,答案存在flow、cost中
	{
		vector<int> p(v.size(),NPOS);
		vector<ll> d(v.size(),INF),h(v.size(),0);
		for(f.assign(e.size(),flow=cost=0);;)
		{
			priority_queue<pair<ll,int> > q;
			for(q.push(make_pair(d[s]=0,s)); !q.empty();)
			{
				ll dis=-q.top().first;
				int u=q.top().second;
				if(q.pop(),d[u]<dis)continue;
				for(int i=0,k,to; i<v[u].a.size(); ++i)
					if(k=v[u].a[i],to=e[k].to,
					        e[k].cap>f[k]&&d[to]>d[u]+e[k].dist+h[u]-h[to])
					{
						d[to]=d[u]+e[k].dist+h[u]-h[to],p[to]=k;
						q.push(make_pair(-d[to],to));
					}
			}
			if(d[t]==INF)return;
			for(int i=0; i<d.size(); ++i)
				if(d[i]!=INF)h[i]+=d[i],d[i]=INF;
			ll _f=INF;
			for(int u=t; u!=s; u=e[p[u]].from)
				_f=min(_f,e[p[u]].cap-f[p[u]]);
			for(int u=t; u!=s; u=e[p[u]].from)
				cost+=_f*e[p[u]].dist,f[p[u]]+=_f,f[p[u]^1]-=_f;
			flow+=_f;
		}
	}
};

上下界有源汇网络流

T向S连容量正无穷的边,将有源汇转化为无源汇。每条边容量减去下界,设 i n [ i ] in[i] 表示流入i的下界之和减去流出i的下界之和。新建超级源汇SS,TT,对于 i n [ i ] &gt; 0 in[i]&gt;0 的点,SS向i连容量为 i n [ i ] in[i] 的边。对于 i n [ i ] &lt; 0 in[i]&lt;0 的点,i向TT连容量为 i n [ i ] −in[i] 的边。
求出以 SS,TT 为源汇的最大流,如果等于 i n [ i ] ( i n [ i ] &gt; 0 ) \sum in[i](in[i] &gt; 0) 则存在可行流。再求出以S,T为源汇的最大流即为最大流。
费用流:建完图后等价于求以SS,TT为源汇的的费用流。
上下界费用流:先把下界的费用加入答案。

线性规划转费用流

首先添加松弛变量,将不等号都变为等号。分别用下一个式子减去上一个式子,如果每个变量只出现了两次且符号一正一负,那么可以转化为费用流。对于每个式子建立一个点,那么每个变量对应一条边,从一个点流出,向另一个点流入。这样,对于等式右边的常数,如果是正的,对应从源点向该点连一条流量C,费用0的边;如果是负的对应从该点向汇点连一条流量−C,费用0的边。对于每个变量,从它系数为正的式子向系数为负的式子连一条容量为 inf,费用为它在目标函数里系数的边。这样网络流模型就构造完毕了。

判断边是否属于某一最小割集

在残余网络 (还有流量的边) 中求强连通分量,顶点不在同一 SCC 且满流的边。
判断边是否为全部最小割集的边: 在上一条的基础上,还要满足起点与 S 在同一 SCC,且终点与T在同一SCC。

二分图

  • 二分图的一个等价定义是:不含有含奇数条边的环的图。
  • 完美匹配图中所有的顶点都是匹配点。
  • 二分图的最小覆盖分为最小顶点覆盖和最小路径覆盖:
  1. 最小点覆盖(最小割)是指最少的顶点数使得二分图G中的每条边都至少与其中一个点相关联。二分图中,最小割=最大匹配。
  2. 最小边覆盖(最大独立集)是指用尽量少的不相交简单路径覆盖二分图中的所有顶点。二分图中,最小点覆盖+最小边覆盖=总点数。
  • Hall定理:二分图中的两部分顶点组成的集合分别为 X,Y ,则有一组无公共点的边,一端恰好为组成 X 的点的充分必要条件是:X 中的任意 k 个点至少与 Y 中的 k 个点相邻。对于区间图只需要考虑极端情况,线段树维护。

匈牙利算法求最大匹配

左边nl个点 0 n l 1 0\ldots nl-1 ,右边nr个点 0 n r 1 0\ldots nr-1 ,取n=max(nl,nr),左i右j间代价a[x][y]
生成fl[i]表示左边第i个匹配右边第fl[i]个(对应权a[i][fl[i]]),fr同理。时间复杂度 O ( n 3 ) O(n^3)
稀疏图的时候考虑用邻接表代替邻接矩阵,并且找完全匹配的时候有问题可直接return。
匹配是一个边集,其中任意两条边都没有公共顶点;扫一遍flfr判断有多少不等于NPOS即为最大匹配数。

struct Hungary:Matrix
{
	int fl[N],fr[N],vr[N];
	bool dfs(int x,int rt)
	{
		for(int y=0; y<n; ++y)
			if(a[x][y]&&vr[y]!=rt)
				if(vr[y]=rt,fr[y]==NPOS||dfs(fr[y],rt))
					return fl[fr[y]=x]=y,1;
		return 0;
	}
	void ask()
	{
		fill(fl,fl+n,NPOS),fill(fr,fr+n,NPOS),fill(vr,vr+n,NPOS);
		for(int i=0; i<n; ++i)if(fl[i]==NPOS)dfs(i,i);
	}
}

HK算法求最大匹配

时间复杂度 O ( V E ) O(\sqrt{|V|}|E|) ,稀疏图上效果明显。

struct HopcroftKarp
{
	vector<int> g[N];
	int n,fl[N],fr[N],hl[N],hr[N],q[N];
	bool dfs(int x)
	{
		for(int i=0,c=hl[x]+1,y=hl[x]=NPOS; i<g[x].size(); ++i)
			if(hr[y=g[x][i]]==c)
				if(hr[y]=NPOS,fr[y]==NPOS||dfs(fr[y]))
					return fl[fr[y]=x]=y,1;
		return 0;
	}
	void ask()
	{
		fill(fl,fl+n,NPOS),fill(fr,fr+n,NPOS);
		for(int x=0; x<n; ++x)
			for(int i=0,y; i<g[x].size(); ++i)
				if(fr[y=g[x][i]]==NPOS)
				{
					fl[fr[y]=x]=y;
					break;
				}
		for(int ql,qr,ok;;)
		{
			fill(hl,hl+n,NPOS),fill(hr,hr+n,NPOS);
			for(int x=ql=qr=ok=0; x<n; ++x)if(fl[x]==NPOS)hl[q[qr++]=x]=0;
			while(ql<qr)
				for(int i=0,x=q[ql++],y; i<g[x].size(); ++i)
					if(hr[y=g[x][i]]==NPOS)
					{
						hr[y]=hl[x]+1;
						if(fr[y]==NPOS)ok=1;
						else if(hl[fr[y]]==NPOS)hl[q[qr++]=fr[y]]=hr[y]+1;
					}
			if(!ok)return;
			for(int x=0; x<n; ++x)if(fl[x]==NPOS)dfs(x);
		}
	}
};

KM算法求最优完备匹配

最大费用流时,a初始化0;最大费用最大流时,a初始化-N*INF

struct KuhnMunkres:Matrix
{
	ll hl[N],hr[N],slk[N];
	int fl[N],fr[N],vl[N],vr[N],pre[N],q[N],ql,qr;
	int check(int i)
	{
		if(vl[i]=1,fl[i]!=NPOS)return vr[q[qr++]=fl[i]]=1;
		while(i!=NPOS)swap(i,fr[fl[i]=pre[i]]);
		return 0;
	}
	void bfs(int s)
	{
		fill(slk,slk+n,INF),fill(vl,vl+n,0),fill(vr,vr+n,0);
		for(vr[q[ql=0]=s]=qr=1;;)
		{
			for(ll d; ql<qr;)
				for(int i=0,j=q[ql++]; i<n; ++i)
					if(!vl[i]&&slk[i]>=(d=hl[i]+hr[j]-a[i][j]))
						if(pre[i]=j,d)slk[i]=d;
						else if(!check(i))return;
			ll d=INF;
			for(int i=0; i<n; ++i)
				if(!vl[i]&&d>slk[i])d=slk[i];
			for(int i=0; i<n; ++i)
			{
				if(vl[i])hl[i]+=d;
				else slk[i]-=d;
				if(vr[i])hr[i]-=d;
			}
			for(int i=0; i<n; ++i)
				if(!vl[i]&&!slk[i]&&!check(i))return;
		}
	}
	void ask()
	{
		fill(fl,fl+n,NPOS),fill(fr,fr+n,NPOS),fill(hr,hr+n,0);
		for(int i=0; i<n; ++i)hl[i]=*max_element(a[i],a[i]+n);
		for(int j=0; j<n; ++j)bfs(j);
	}
};

关键点

关键点即一定在最大匹配中的点。
求出任意一个最大匹配,那么只需要考虑哪些匹配点不一定在。
假设是考虑左侧的点,右侧类似:
将匹配边从右往左,非匹配边从左到右,从左侧每个未匹配点开始DFS到的匹配点都不是关键点。

关键边

求出任意一个最大匹配,将匹配边从右到左,剩余边从左到右,求出 SCC。 对于一条边:
若它位于当前匹配中,那么若两端点位于同一SCC,则是可能在,否则必定在;若它不位于当前匹配中,那么若两端点位于同一 SCC,则是可能在,否则必定不在。

带花树求最大匹配

struct Blossom:Graph
{
	vector<int> f,vis,pre,flag;
	deque<int> q;
	UnionfindSet ufs;
	Blossom(int n):Graph(n) {}
	void poi(int x,int a)
	{
		for(int y,z; x!=a; ufs.merge(y,x),ufs.merge(x=z,y))
		{
			if(ufs.fa(z=pre[y=f[x]])!=a)pre[z]=y;
			if(!flag[y])flag[y]=1,q.push_back(y);
			if(!flag[z])flag[z]=1,q.push_back(z);
		}
	}
	void ask()
	{
		f.assign(v.size(),NPOS),vis=f;
		for(int s=0,t=0; s<v.size(); ++s)
			if(f[s]==NPOS)
				for(ufs.init(v.size()),pre.assign(v.size(),NPOS),flag=pre,q.assign(flag[s]=1,s);
				        f[s]==NPOS&&!q.empty(); q.pop_front())
					for(int i=0,x=q.front(),y,a,b; i<v[x].a.size(); ++i)
						if((y=e[v[x].a[i]].to)!=f[x]&&flag[y]&&ufs.fa(x)!=ufs.fa(y))
						{
							if(flag[y]==1)
							{
								for(a=x,b=y,++t;; swap(a,b))
									if(a!=NPOS)
									{
										if(vis[a=ufs.fa(a)]==t)break;
										vis[a]=t,a=f[a]!=NPOS?pre[f[a]]:NPOS;
									}
								if(ufs.fa(x)!=a)pre[x]=y;
								if(ufs.fa(y)!=a)pre[y]=x;
								poi(x,a),poi(y,a);
							}
							else if(f[y]==NPOS)
							{
								for(pre[y]=x; y!=NPOS;)swap(y,f[f[y]=pre[y]]);
								break;
							}
							else pre[y]=x,q.push_back(f[y]),flag[f[y]]=1,flag[y]=0;
						}
	}
};

欧拉路

给定无孤立结点图G,若存在一条路,经过图中每边一次且仅一次,该条路称为欧拉路。

  • 无向图:当仅当该图所有顶点的度数为偶数(此时为回路),或除两个度数为奇数外(作为路径的起点和终点)、其余全为偶数。
  • 有向图:当仅当该图所有顶点出度=入度(此时为回路),或一个顶点出度=入度+1(作为起点)、另一个顶点入度=出度+1(作为终点)、其他顶点出度=入度。
struct Fleury:Graph
{
	vector<int> vis,cur,p;
	Fleury(int n):Graph(n) {}
	void dfs(int u)
	{
		for(int &i=cur[u],k; i<v[u].b.size(); ++i)//遍历原图的反向图,这里加了一个“当前弧”优化
			if(k=v[u].b[i],!vis[k])
			{
				vis[k]/*=vis[k^1]=*/1;//无向图去掉注释,即同时标记反向边
				dfs(e[k].from);
				p.push_back(k);
			}
	}
	void ask()//查询欧拉回路,路径上边的序号按顺序存在p中
	{
		vis.assign(e.size(),0),cur.assign(v.size(),0),p.clear();
		for(int i=0; i<v.size(); ++i)
			if(v[i].b.size()%2)
				return dfs(i);
		dfs(0);
	}
};

混合图欧拉回路判定

首先给无向边随便定一个方向,设 deg x \deg x 为x连出去的边数−连入x的边数。若存在 deg x \deg x 为奇数,或者图不连通,则无解。否则建立源点S,汇点T。对于一个点x,若 deg x &gt; 0 \deg x&gt;0 ,则S向x连边,容量 deg x 2 \frac{\deg x}{2} ;若 deg x &lt; 0 \deg x&lt;0 ,则x向T连边,容量 deg x 2 -\frac{\deg x}{2} 。 对于一条定了向的无向边 x y x\to y ,x向y连边,容量1,求出最大流,若与 S 和T连的每条边都满流,则有解。

数论

辗转相除法与同余系

裴蜀定理:对任何 a , b Z a,b\in Z 和它们的最大公约数 d d ,关于未知数 x , y x,y 的线性不定方程(称为裴蜀等式): a x + b y = c ax+by=c 当仅当 d c d\mid c ,可知有无穷多解。特别地, a x + b y = d ax+by=d 一定有解。
推论 a , b a,b 互质的充要条件是 a x + b y = 1 ax+by=1 有整数解。

ll gcd(ll a,ll b)//a、b的最大公约数
{
	return b?gcd(b,a%b):a;
}
ll lcm(ll a,ll b)//a、b的最小公倍数
{
	return a/gcd(a,b)*b;
}
ll gcd(ll a,ll b,ll &x,ll &y)//扩展欧几里得,引用返回a*x+b*y=gcd(a,b)绝对值之和最小的解
{
	if(!a)return x=0,y=1,b;
	ll d=gcd(b%a,a,y,x);
	return x-=y*(b/a),d;
}

同余系运算

求乘法逆元的另外一种方法是用欧拉定理 x ϕ ( m ) 1 ( m o d m ) x^{\phi(m)}\equiv1\pmod m ,x的逆是 x ϕ ( m ) 1 x^{\phi(m)-1} 。特别地,m为素数时 ϕ ( m ) = m 1 \phi(m)=m-1 ,此时x的逆就是pow(x,m-2,m)
log函数:m为素数时求解模方程 a x b ( m o d m ) a^x\equiv b\pmod m 。设P为质数,G为P的原根,则 x y b ( m o d P ) x^y\equiv b\pmod P 等价于 y   i n d   x b ( m o d P 1 ) y\ ind\ x\equiv b\pmod{P-1} ,其中 G   i n d   x x ( m o d P ) G\ ind\ x\equiv x\pmod P

ll add(ll a,ll b,ll m)
{
	if(a+=b,a<0)a=m-(-a%m);//负数取模和编译器有关,这样才是需要的同余系运算
	return a%m;
}
ll mul(ll a,ll b,ll m)//根据a*b是否爆ll替换a*b%m
{
	ll r=0;
	for(a%=m; b; b>>=1,a=add(a,a,m))
		if(b&1)r=add(r,a,m);
	return r;
}
ll pow(ll a,ll b,ll m)
{
	ll r=1;
	for(a%=m; b; b>>=1,a=mul(a,a,m))
		if(b&1)r=mul(r,a,m);
	return r;
}
ll sub(ll a,ll b,ll m)
{
	return add(a,-b,m);
}
ll inv(ll a,ll m)//模m下a的乘法逆元,不存在返回-1(m为素数时a不为0必有逆元)
{//return pow(a,phi(m)-1,m);
	ll x,y,d=gcd(a,m,x,y);
	return d==1?(x+m)%m:-1;
}
ll div(ll a,ll b,ll m)
{
	return mul(a,inv(b,m),m);
}
ll log(ll a,ll b,ll m)
{
	ll n=ceil(sqrt(m+0.5));
	map<ll,ll> x;
	for(ll i=0,e=1; i<n; e=mul(e,a,m),++i)
		if(!x.count(e))x[e]=i;
	for(ll i=0,v=inv(pow(a,n,m),m); i<n; ++i,b=mul(b,v,m))
		if(x.count(b))return i*n+x[b];
	return -1;
}

同余方程

void sol(ll a,ll b,ll n,vector<ll> &ans)//返回ax=b(mod n)循环节内所有解
{
	ll x,y,d=gcd(a,n,x,y);
	if(ans.clear(),b%d)return;
	ans.push_back((b/d)%(n/d)*(x=(x%n+n)%n));
	for(int i=1; i<d; ++i)
		ans.push_back((ans[0]+i*n/d)%n);
}

同余方程组

ll sol(const vector<pair<ll,ll> > &v)//x%v[i].first==v[i].second,不存在返回-1
{
	ll m=v[0].first,r=v[0].second,c,d,x,y,z;
	for(int i=1; i<v.size(); ++i)
	{
		if(c=v[i].second-r,d=gcd(m,v[i].first,x,y),c%d)
			return -1;
		gcd(m/d,z=v[i].first/d,x,y),r+=c/d*x%z*m,r%=m*=z;
	}
	return r<0?r+m:r;
}

欧拉筛

欧拉函数 ϕ ( n ) \phi(n) 是小于n的正整数中与n互素的数的数目。特别地,规定 ϕ ( 1 ) = 1 \phi(1)=1 ,易知n>2时都为偶数。
欧拉函数是积性函数,即对任意素数 p , q p,q 满足下列关系: ϕ ( p q ) = ϕ ( p ) ϕ ( q ) = ( p 1 ) ( q 1 ) \phi(pq)=\phi(p)\phi(q)=(p-1)(q-1) 对任何两个互质的正整数 x , m ( m 2 ) x, m(m\geq2) 有欧拉定理: x ϕ ( m ) 1 ( m o d m ) x^{\phi(m)}\equiv1\pmod m 当m为素数p时,此式变为费马小定理: x p 1 1 ( m o d p ) x^{p-1}\equiv1\pmod p 利用欧拉函数和它本身不同质因数的关系,用筛法 O ( N ) O(N) 预处理某个范围内所有数的欧拉函数值,并求出素数表。同时,利用计算欧拉函数过程中求出的最小素因子m,可以实现 O ( l o g N ) O(log N) 的素因数分解。
更新:增加同时求莫比乌斯函数 μ ( n ) \mu(n) 的代码,存在mu

struct EulerSieve
{
	vector<int> p,m,phi,mu;//素数序列,最小素因子,欧拉函数,莫比乌斯函数
	EulerSieve(int N):m(N,0),phi(N,0),mu(N,0)
	{
		phi[1]=mu[1]=1;//m[1]=0
		for(long long i=2,k; i<N; ++i)//防i*p[j]爆int
		{
			if(!m[i])p.push_back(m[i]=i),phi[i]=i-1,mu[i]=-1;//i是素数
			for(int j=0; j<p.size()&&(k=i*p[j])<N; ++j)
			{
				phi[k]=phi[i]*p[j];
				if((m[k]=p[j])==m[i])
				{
					mu[k]=0;
					break;
				}
				phi[k]-=phi[i];
				mu[k]=-mu[i];
			}
		}
	}
};

常见数论函数变换

d n μ ( d ) = [ n = 1 ] \sum_{d|n}\mu(d)=[n=1]
ϕ ( n ) = i = 1 n [ gcd ( i , n ) = 1 ] = i = 1 n k i , k n μ ( k ) = k n n k μ ( k ) \phi(n)=\sum_{i=1}^n[\gcd(i,n)=1]=\sum_{i=1}^n\sum_{k\mid i,k\mid n}\mu(k)=\sum_{k\mid n}\frac nk\mu(k)

莫比乌斯反演

f ( n ) = d n g ( d ) f(n)=\sum_{d|n}g(d) ,则 g ( n ) = d n μ ( d ) f ( n d ) g(n)=\sum_{d|n}\mu(d)f(\frac{n}{d})
f ( n ) = i = 1 n t ( i ) g ( n i ) f(n)=\sum_{i=1}^nt(i)g(\frac{n}{i}) ,则 g ( n ) = i = 1 n μ ( i ) t ( i ) f ( n i ) g(n)=\sum_{i=1}^n\mu(i)t(i)f(\frac{n}{i}) (此时代 t ( i ) = [ g c d ( n , i ) &gt; 1 ] t(i)=[gcd(n,i)&gt;1] 可得上式)
举例(其中 T = k d T=kd ): i = 1 n j = 1 m gcd ( i , j ) = d d i = 1 n j = 1 m [ gcd ( i , j ) = d ] = d d i = 1 n d j = 1 m d [ gcd ( i , j ) = 1 ] = d d i = 1 n d j = 1 m d k i , k j μ ( k ) = d d k μ ( k ) k i n d k j m d = d d k μ ( k ) n k d m k d = T n T m T k T T k μ ( k ) = T n T m T φ ( T ) \begin{aligned} \sum_{i=1}^n\sum_{j=1}^m\gcd(i,j)&amp;=\sum_d d\sum_{i=1}^n\sum_{j=1}^m[\gcd(i,j)=d] \\ &amp;=\sum_{d}d\sum_{i=1}^{\lfloor\frac nd\rfloor}\sum_{j=1}^{\lfloor\frac md\rfloor}[\gcd(i,j)=1] \\ &amp;=\sum_{d}d\sum_{i=1}^{\lfloor\frac nd\rfloor}\sum_{j=1}^{\lfloor\frac md\rfloor}\sum_{k\mid i,k\mid j}\mu(k) \\ &amp;=\sum_d d\sum_k\mu(k)\sum_{k\mid i}^{\lfloor\frac nd\rfloor}\sum_{k\mid j}^{\lfloor\frac md\rfloor} \\ &amp;=\sum_{d}d\sum_k\mu(k)\lfloor\frac n{kd}\rfloor\lfloor\frac m{kd}\rfloor\\&amp;=\sum_{T}\lfloor\frac nT\rfloor\lfloor\frac mT\rfloor\sum_{k\mid T}\frac Tk\mu(k) \\ &amp;=\sum_{T}\lfloor\frac nT\rfloor\lfloor\frac mT\rfloor\varphi(T) \end{aligned}
φ ( T ) \varphi(T) 可以使用线性筛预处理处理,我们就可以枚举 T T 求上式了,时间复杂度 O ( n ) O(n) 。多组数据 n , m n,m 询问上式,时间复杂度就变成了 O ( T n ) O(Tn) 。事实上, n T \lfloor\frac{n}{T}\rfloor 是不会轻易变化的,是过了连续的一段后才发生变化的,那么我们就可以计算出这一段的结束位置,对 φ \varphi 函数作前缀和,就可以直接分块了,这样的时间复杂度是 O ( T n ) O(T\sqrt{n}) 的。

前缀和

欧拉函数前缀和 S ϕ ( n ) = ( n + 1 ) n 2 d = 1 n S ϕ ( n d ) S_\phi(n)=\frac{(n+1)n}2-\sum_{d=1}^nS_\phi(\frac{n}{d})
莫比乌斯函数前缀和 S μ ( n ) = 1 d = 1 n S μ ( n d ) S_\mu(n)=1-\sum_{d=1}^nS_\mu(\frac{n}{d})

PollardRho大数素因子分解

时间复杂度 O ( N 1 / 4 ) O(N^{1/4}) ,数据多的时候可考虑欧拉筛优化。

struct PollardRho
{
	bool isPrime(ll n,int S=12)//MillerRabin素数测试,S为测试次数,用前S个素数测一遍,S=12可保证unsigned long long范围内无错;n<2请特判
	{
		ll d,u,t,p[]= {2,3,5,7,11,13,17,19,23,29,31,37};
		for(d=n-1; !(d&1);)d>>=1;//未对0,1做特判!
		for(int i=0; i<S; ++i)
		{
			if(n%p[i]==0)return n==p[i];
			for(u=d,t=pow(p[i],u,n); u!=n-1&&t!=n-1&&t!=1; u<<=1)
				t=mul(t,t,n);
			if(t!=n-1&&!(u&1))return 0;
		}
		return 1;
	}
	void fac(ll n,vector<ll> &factor)
	{
		if(isPrime(n))return factor.push_back(n);
		for(ll c=1;; ++c)
			for(ll i=0,k=1,x=rand()%(n-1)+1,y,p;;)
			{
				if(++i==k)y=x,k<<=1;
				if(x=(mul(x,x,n)+c)%n,p=gcd(abs(x-y),n),p==n)break;
				if(p>1)return fac(p,factor),fac(n/p,factor);
			}
	}
};

快速傅里叶变换(FFT)

template<typename T>
void rader(vector<T> &x)///雷德算法,x.size()为2的整数次幂,将 x[i]与x[j]交换,其中j为“i的二进制反转之后”的数
{
	for(int i=0,j=0,k; i<x.size()-1; ++i,j+=k)
	{
		if(i<j)swap(x[i],x[j]);
		for(k=x.size()>>1; k<=j; k>>=1)j-=k;
	}
}
void fft(vector<complex<double> > &x,int inv)//x.size()为2的整数次幂,x[i]为系数,inv==1时为DFT,否则iDFT
{
	rader(x);
	if(inv!=1)inv=-1;
	for(int i=1,j,k; i<x.size(); i<<=1)
		for(complex<double> u=polar(1.0,acos(-1)/i*inv),w=1,t=j=0;
		        j<i; ++j,w*=u)
			for(k=j; k<x.size(); k+=i<<1)
				t=w*x[k+i],x[k+i]=x[k]-t,x[k]+=t;
	if(inv==1)return;
	for(int i=0; i<x.size(); ++i)x[i]/=x.size();
}

优化高精度乘法

常规算法

Wint& operator*=(Wint &a,const Wint &b)//fft优化乘法,注意double仅15位有效数字,调小Wint::width不超过2,计算自2*log2(base)+2*log2(len)<53
{
	int len=1;
	while(len<a.size()||len<b.size())len*=2;
	len*=2;
	vector<complex<double> > ax(a.begin(),a.end()),bx(b.begin(),b.end());
	ax.resize(len),fft(ax,1);
	bx.resize(len),fft(bx,1);
	for(int i=0; i!=len; ++i)ax[i]*=bx[i];
	fft(ax,-1),a.clear();
	for(int i=1; i!=len; ++i)
	{
		unsigned long long n=ax[i-1].real()+0.5;
		ax[i]+=n/a.base,a.push_back(n%a.base);
	}
	a.push_back(ax.back().real()+0.5);
	return a.trim(0);
}

快速数论变换(FNTT)

原理和FFT相同,解决特殊情况下FFT的浮点误差,并且可以在同余系进行变换。
对于形如 m = 2 n c + 1 m=2^nc+1 的费马素数,记其原根为g,则旋转因子为 g ( m 1 ) / n g^{(m-1)/n} ,满足 g m 1 = 1 g^{m-1}=1 2 n m 1 2^n|m-1
常见素数的原根

void fntt(vector<ll> &x,ll inv,ll m,ll g)//x.size()为2的整数次幂,x[i]为系数,inv==1时为NTT,否则iNTT;费马素数,原根
{
	rader(x);
	if(inv!=1)inv=m-2;
	for(int i=1,j; i<x.size(); i<<=1)
		for(ll u=pow(g,mul(m/2/i,inv,m-1),m),w=1,t=j=0;
		        j<i; ++j,w=mul(w,u,m))
			for(int k=j; k<x.size(); k+=i<<1)
			{
				t=mul(w,x[k+i],m);
				if(x[k+i]=x[k]-t,x[k+i]<0)x[k+i]+=m;
				if(x[k]+=t,x[k]>=m)x[k]-=m;
			}
	if(inv==1)return;
	ll u=pow(x.size(),m-2,m);
	for(int i=0; i<x.size(); ++i)x[i]=mul(x[i],u,m);
}

优化高精度乘法

常规算法

Wint& operator*=(Wint &a,const Wint &b)//ntt优化,Wint::width不超过2
{
	ll len=1,m=(7<<26)+1,g=3;//费马素数,原根
	while(len<a.size()||len<b.size())len*=2;
	len*=2;
	vector<ll> ax(a.begin(),a.end()),bx(b.begin(),b.end());
	ax.resize(len),fntt(ax,1,m,g);
	bx.resize(len),fntt(bx,1,m,g);
	for(int i=0; i!=len; ++i)ax[i]=mul(ax[i],bx[i],m);
	fntt(ax,-1,m,g),a.clear();
	for(int i=1; i!=len; ++i)
		ax[i]+=ax[i-1]/a.base,a.push_back(ax[i-1]%a.base);
	a.push_back(ax.back());
	return a.trim(0);
}

快速沃尔什变换(FWT)

再给一个二分的代码便于理解。

void fwt(vector<ll> &x,void f(ll &l,ll &r))
{
	for(int i=1; i<x.size(); i<<=1)
		for(int j=0; j<i; ++j)
			for(int k=j; k<x.size(); k+=i<<1)
				f(x[k],x[k+i]);
}
void fwt(ll *b,ll *e,void f(ll &l,ll &r))
{
	if(e-b<2)return;
	ll *m=b+(e-b)/2;
	fwt(b,m,f),fwt(m,e,f);
	while(m<e)f(*(b++),*(m++));
}

XOR

可能要在同余系中进行运算,下面代码需要修改。

void tf(ll &l,ll &r)
{
	ll tl=l+r,tr=l-r;
	l=tl,r=tr;
}
void utf(ll &l,ll &r)
{
	tf(l,r),l>>=1,r>>=1;
}

AND

void tf(ll &l,ll &r)
{
	l+=r;
}
void utf(ll &l,ll &r)
{
	l-=r;
}

OR

void tf(ll &l,ll &r)
{
	r+=l;
}
void utf(ll &l,ll &r)
{
	r-=l;
}

XNOR,NAND,NOR

直接用异或运算、与运算、或运算的方法求出来,然后将互反的两位交换即可。

Pell方程

形如 x 2 D y 2 = 1 x^2-Dy^2=1 (D为任意正整数)的方程称为佩尔方程,必有最小正整数解 ( x 0 , y 0 ) (x_0,y_0) ,用 x n = x 0 x n 1 + D y 0 y n 1 , y n = y 0 x n 1 + x 0 y n 1 x_n=x_0x_{n-1}+Dy_0y_{n-1},y_n=y_0x_{n-1}+x_0y_{n-1} 可递推方程的第n小整数解(可用矩阵快速幂求),同时还有
2 x 0 x n = x n 1 + x n + 1 , 2 x 0 y n = y n 1 + y n + 1 2x_0x_n=x_{n-1}+x_{n+1},2x_0y_n=y_{n-1}+y_{n+1}

Jacobi’s Four Square Theorem

a 2 + b 2 + c 2 + d 2 = n a^2 + b^2 + c^2 + d^2 = n 的自然数解个数为 r4(n),d(n) 为 n 的约数和,由 Jacobi’s Four Square Theorem 可知,若 n 是奇数,则 r4(n) = 8d(n),否则 r4(n) = 24d(k),k 是 n 去除所有 2 后的结果。

博弈论常用SG函数

一个局面的 SG 为 mex(后继局面的 SG),mex 运算为集合中没出现的最小的自然数。几个 局面的和的 SG 为单个的 SG 异或,SG 不为 0 时先手必胜,SG 为 0 时后手必胜。

Nim Game

n 堆石子,每次可以从一堆里面取任意个石子。对于一堆石子,SG 函数就是石子数。整个 游戏的 SG 函数是每一堆石子的 SG 函数的异或和。 必胜:SG 不为 0,必败:SG 为 0。

Bash Game

每次最多取 m 个石子,其他同 Nim。一堆石子的 SG 函数为石子数 mod(m + 1)。 必胜:SG 不为 0,必败:SG 为 0。

Nim-k Game

每次最多可以同时从 k 堆石子进行操作,这 k 堆可以取不同数量的石子。 一堆石子的 SG 函数为石子数,对每一个二进制位单独算,求 SG 函数每一个二进制位 1 的个数 mod(k + 1),如果都为 0,则必败,否则必胜。

Anti-Nim Game

不能取石子的一方获胜。 必胜:SG 不为 0 且至少有一堆石子数大于 1,SG 为 0 且每一堆石子数都不超过 1 必败:其余为必败。

Anti-SG Game SG

游戏中最先不能行动的一方获胜。 必胜:SG 不为 0 且至少有一个游戏的 SG 大于 1,SG 为 0 且每一个游戏的 SG 都不超过 1 必败:其余为必败。

Staircase Nim

阶梯博弈,每次可以从一个阶梯上拿掉任意数量石子放到下一层阶梯,不能操作的为输。 SG 函数为奇数阶梯上的石子的异或和,如果移动偶数层的石子到奇数层,对手一定可以 继续移动这些石子到偶数层,使得其 SG 不变。 必胜:SG 不为 0,必败:SG 为 0。

Lasker’s Nim Game

n 堆石子,每次可以从一堆里面取任意个石子,或者选择某堆至少为 2 的石子,分成两堆 非空石子。 SG(0) = 0,SG(1) = 1,SG(2) = 2,SG(3) = 4。 对于 k ≥ 1,SG(4k) = 4k−1,SG(4k+1) = 4k+1,SG(4k+2) = 4k+2,SG(4k+3) = 4k+4。

Wythff Game

有两堆石子,每次可以从一堆或者两堆里拿走一样数目的石子,不能取的为输。 必败态为 (1,2)(3,5)(4,7)(6,10)…,差为 1、2、3、4… 每一对数的第一个数为前面没出现的最小的正整数。
递推公式:a[k]=⌊k(1+√5)/2⌋,b[k]=a[k]+k

翻硬币游戏

n 枚硬币排成一排,有的正面朝上,有的反面朝上。游戏者根据某些约束翻硬币(如:每 次只能翻一或两枚,或者每次只能翻连续的几枚),但他所翻动的硬币中,最右边的必须是从正 面翻到反面。谁不能翻谁输。 需要先开动脑筋把游戏转化为其他的取石子游戏之类的,然后用如下定理解决: 局面的 SG 值等于局面中每个正面朝上的棋子单一存在时的 SG 值的异或和。

每一次只能翻转一枚硬币

SG(0) = 0,SG(k) = 1(k > 0)。

每一次可以翻转一枚或两枚硬币

SG(n) = n。

Twins Game

每次必须翻动两个硬币,而且这两个硬币的距离要在可行集 S = 1,2,3 中,相当于 Bash Game。

每一次必须翻连续的 n 个硬币

SG(nk) = 1(k > 0),其他 SG 函数值为 0。

Ruler Game

每一次可以翻任意长度的连续一段硬币,SG(x) 为 x 中包含的 2 的最高次幂,即 SG(x) = ⌊log2 x⌋+ 1

离散数学

归并排序求逆序对

ll merge_sort(ll *b,ll *e)//int存答案可能会爆
{
	if(e-b<2)return 0;
	ll i=0,j=0,*m=b+(e-b)/2,ans=merge_sort(b,m)+merge_sort(m,e);
	vector<ll> l(b,m),r(m,e);
	while(i<l.size()&&j<r.size())
	{
		if(r[j]<l[i])*(b++)=r[j++],ans+=l.size()-i;
		else *(b++)=l[i++];
	}
	while(i<l.size())*(b++)=l[i++];
	while(j<r.size())*(b++)=r[j++];
	return ans;
}

快速解约瑟夫

ll josephus(ll n,ll k)//编号0~n-1,每k个出列,时间复杂度O(min(n,k))
{
	if(n<3)return k%n;
	if(n<k)return (Josephus(n-1,k)+k)%n;
	ll ret=Josephus(n-n/k,k)-n%k;
	return ret<0?ret+n:ret+ret/(k-1);
}

曼哈顿距离的变换

x 1 x 2 + y 1 y 2 = m a x ( ( x 1 + y 1 ) ( x 2 + y 2 ) , ( x 1 y 1 ) ( x 2 y 2 ) ) |x1 −x2|+|y1 −y2| = max(|(x1 + y1)−(x2 + y2)|,|(x1 −y1)−(x2 −y2)|)

皮克定理

顶点坐标均是整点(或说正方形格点)的简单多边形中,面积S和内部格点数目n、边上格点数目m的满足关系 S = n + m 2 1 S=n+\frac{m}{2}-1

蔡勒公式

w = ( c 4 2 c + y + y 4 + 13 ( m + 1 ) 5 + d 1 ) m o d &ThinSpace;&ThinSpace; 7 w=(\lfloor\frac{c}{4}\rfloor-2c+y+\lfloor\frac{y}{4}\rfloor+\lfloor\frac{13(m+1)}{5}\rfloor+d-1)\mod7
w: 0 , 1 , , 6 0,1,\ldots,6 对应周日,周一, \ldots ,周六
c:世纪减1(即年份前两位数)。
y:年份后两位数。
m:月( 3 m 14 3\leq m\leq14 ,即在蔡勒公式中,1、2月要看作上一年的13、14 月来计算)。
d:日。

数学分析

增长趋势

n + , p , q &gt; 0 , a &gt; 1 , ( ln n ) q n p a n n ! n n n\to+\infty,\forall p,q&gt;0,a&gt;1,{(\ln n)}^q\ll n^p\ll a^n\ll n!\ll n^n

积分表

反读可得导数表,此处略。
k &ThinSpace; d x = k x + C \int k\,\mathrm{d}x=kx+C
x a &ThinSpace; d x = x a + 1 a + 1 + C \int x^a\,dx=\frac{x^{a+1}}{a+1}+C
1 x &ThinSpace; d x = ln x + C \int\frac{1}{x}\,dx=\ln|x|+C
e x &ThinSpace; d x = e x + C \int e^x\,dx=e^x + C
a x &ThinSpace; d x = a x ln a + C \int a^x\,dx=\frac{a^x}{\ln a}+C
cos x &ThinSpace; d x = sin x + C \int\cos x\,dx=\sin x+C
sin x &ThinSpace; d x = cos x + C \int\sin x\,dx=-\cos x+C
1 c o s 2 x &ThinSpace; d x = sec 2 x &ThinSpace; d x = tan x + C \int\frac{1}{cos^2x}\,dx=\int\sec^2 x\,dx=\tan x+C
1 s i n 2 x &ThinSpace; d x = csc 2 x &ThinSpace; d x = cot x + C \int\frac{1}{sin^2x}\,dx=\int\csc^2 x\,dx=-\cot x+C
1 1 x 2 &ThinSpace; d x = arcsin x + C = arccos x + C \int\frac{1}{\sqrt{1-x^2}}\,dx=\arcsin x+C=-\arccos x + C
1 1 + x 2 &ThinSpace; d x = arctan x + C = a r c c o t &ThinSpace; x + C \int\frac{1}{1+x^2}\,dx=\arctan x+C=-arccot\,x+C
sec x tan x &ThinSpace; d x = sec x + C \int\sec x\tan x\,dx=\sec x+C
csc x cot x &ThinSpace; d x = csc x + C \int\csc x\cot x\,dx=-\csc x+C
tan x &ThinSpace; d x = ln cos x + C \int\tan x\,dx=-\ln|\cos x|+C
cot x &ThinSpace; d x = ln sin x + C \int\cot x\,dx=\ln|\sin x|+C
sec x &ThinSpace; d x = ln sec x + tan x + C \int\sec x\,dx=\ln|\sec x+\tan x|+C
csc x &ThinSpace; d x = ln csc x cot x + C \int\csc x\,dx=\ln|\csc x-\cot x|+C
s h &ThinSpace; x &ThinSpace; d x = c h &ThinSpace; x + C \int sh\,x\,dx=ch\,x+C
c h &ThinSpace; x &ThinSpace; d x = s h &ThinSpace; x + C \int ch\,x\,dx=sh\,x+C
1 x 2 + a 2 &ThinSpace; d x = 1 a arctan x a + C \int\frac{1}{x^2+a^2}\,dx=\frac{1}{a}\arctan\frac{x}{a}+C
1 x 2 a 2 &ThinSpace; d x = 1 2 a ln x a x + a + C \int\frac{1}{x^2-a^2}\,dx=\frac{1}{2a}\ln|\frac{x-a}{x+a}| + C
1 a 2 x 2 &ThinSpace; d x = arcsin x a + C \int\frac{1}{\sqrt{a^2-x^2}}\,dx=\arcsin\frac{x}{a}+C
1 x 2 a 2 &ThinSpace; d x = ln x + x 2 a 2 + C \int\frac{1}{\sqrt{x^2-a^2}}\,dx=\ln|x+\sqrt{x^2-a^2}|+C
1 x 2 + a 2 &ThinSpace; d x = ln x + x 2 + a 2 + C \int\frac{1}{\sqrt{x^2+a^2}}\,dx=\ln|x+\sqrt{x^2+a^2}|+C

积分求几何量

面积

若简单闭曲线 { x = x ( t ) , y = y ( t ) , t [ α , β ] \begin{cases}x=x(t),\\y=y(t),\end{cases}t\in[\alpha,\beta] 端点处连续( x ( α ) = x ( β ) , y ( α ) = y ( β ) x(\alpha)=x(\beta),y(\alpha)=y(\beta) )且其他地方不自交, x ( t ) , y ( t ) x(t),y(t) 都逐段有连续微商,则此闭合曲线围起来的有界区域面积 S = α β x ( t ) y ( t ) &ThinSpace; d t = α β y ( t ) &ThinSpace; d x ( t ) = Γ y &ThinSpace; d x = Γ x &ThinSpace; d y S=-\int_\alpha^\beta x&#x27;(t)y(t)\,dt=-\int_\alpha^\beta y(t)\,dx(t)=-\oint_\Gamma y\,dx=\oint_\Gamma x\,dy 等式右边称为曲线 Γ \Gamma 上的积分,其计算方法是带入参数方程到定积分计算式中,积分上下限为始点与终点对应的参数值。下限并不总是小于上限,参数从下限到上限变化时对应曲线的正向(沿正向观察时,曲线所围的区域永远在左侧)。
极坐标下,连续非负曲线 r = r ( θ ) r=r(\theta) 与向径 θ = α , θ = β \theta=\alpha,\theta=\beta ,其中 0 β α 2 π 0\leq\beta-\alpha\leq2\pi 所围成的平面图形面积 S = 1 2 α β r 2 ( θ ) &ThinSpace; d θ S=\frac{1}{2}\int_\alpha^\beta r^2(\theta)\,d\theta

体积

记立体过x点且垂直于x轴的截面面积为 S ( x ) S(x) ,则其体积 V = a b S ( x ) &ThinSpace; d x V=\int_a^bS(x)\,dx 连续曲线 y = f ( x ) 0 , x [ a , b ] y=f(x)\ge 0,x\in[a,b] 绕x轴旋转一周产生的旋转体体积 V = π a b y 2 &ThinSpace; d x V=\pi\int_a^by^2\,dx

弧长

若简单闭曲线 { x = x ( t ) , y = y ( t ) , t [ α , β ] \begin{cases}x=x(t),\\y=y(t),\end{cases}t\in[\alpha,\beta] 端点处重合( x ( α ) = x ( β ) , y ( α ) = y ( β ) x(\alpha)=x(\beta),y(\alpha)=y(\beta) )且其他地方不自交, x ( t ) , y ( t ) x(t),y(t) 连续且满足 [ x ( t ) ] 2 + [ y ( t ) ] 2 0 , t [ α , β ] [x&#x27;(t)]^2+[y&#x27;(t)]^2\ne0,\forall t\in[\alpha,\beta] 此时称曲线光滑,其长度 s = α β [ x ( t ) ] 2 + [ y ( t ) ] 2 &ThinSpace; d t s=\int_\alpha^\beta\sqrt{[x&#x27;(t)]^2+[y&#x27;(t)]^2}\,dt 此式可对称推广到高维空间曲线。
极坐标下, r = r ( θ ) , θ [ α , β ] r=r(\theta),\theta\in[\alpha,\beta] 的长度为 s = α β [ r ( θ ) ] 2 + [ r ( θ ) ] 2 &ThinSpace; d θ s=\int_\alpha^\beta\sqrt{[r(\theta)]^2+[r&#x27;(\theta)]^2}\,d\theta

曲率

若曲线由参数方程 { x = x ( t ) , y = y ( t ) , t [ α , β ] \begin{cases}x=x(t),\\y=y(t),\end{cases}t\in[\alpha,\beta] 给出且有二阶微商,则其在一点的曲率 K = y x y x [ x 2 + y 2 ] 3 2 K=\frac{|y&#x27;&#x27;x&#x27;-y&#x27;x&#x27;&#x27;|}{[x&#x27;^2+y&#x27;^2]^{\frac{3}{2}}} y = f ( x ) y=f(x) ,则 K = y ( 1 + y 2 ) 3 2 K=\frac{|y&#x27;&#x27;|}{(1+y&#x27;^2)^\frac{3}{2}} 同时记 1 K \frac{1}{K} 为曲率半径。

旋转体侧面积

若曲线由参数方程 { x = x ( t ) , y = y ( t ) , t [ α , β ] \begin{cases}x=x(t),\\y=y(t),\end{cases}t\in[\alpha,\beta] 给出,则其绕x轴旋转体的侧面积 s = 2 π α β y ( t ) [ x ( t ) ] 2 + [ y ( t ) ] 2 &ThinSpace; d t s=2\pi\int_\alpha^\beta y(t)\sqrt{[x&#x27;(t)]^2+[y&#x27;(t)]^2}\,dt

空间曲线的切线与法平面

若已知曲线上一点 P ( x 0 , y 0 , z 0 ) P(x_0,y_0,z_0) 处的切向量为 τ ( x 0 , y 0 , z 0 ) = ( A , B , C ) \tau(x_0,y_0,z_0)=(A,B,C) 则曲线在该点的切线方程为 x x 0 A = y y 0 B = z z 0 C \frac{x-x_0}A=\frac{y-y_0}B=\frac{z-z_0}C 法平面方程为 A ( x x 0 ) + B ( y y 0 ) + C ( z z 0 ) = 0 A(x-x_0)+B(y-y_0)+C(z-z_0)=0 当曲线由参数方程 { x = x ( t ) , y = y ( t ) , z = z ( t ) , t [ α , β ] \begin{cases}x=x(t),\\y=y(t),\\z=z(t),\end{cases}t\in[\alpha,\beta] 给出时,曲线在P点的切向量为 τ = ± ( x ( t 0 ) , y ( t 0 ) , z ( t 0 ) ) \tau=\pm(x&#x27;(t_0),y&#x27;(t_0),z&#x27;(t_0)) 更一般地,若曲线用两曲面的交线给出 { F ( x , y , z ) = 0 , G ( x , y , z ) = 0 , \begin{cases}F(x,y,z)=0,\\G(x,y,z)=0,\end{cases} 且在P点的某邻域能确定函数组 y = y ( x ) , z = z ( x ) y=y(x),z=z(x) 满足 y 0 = y ( x 0 ) , z 0 = z ( x 0 ) y_0=y(x_0),z_0=z(x_0) ,且 y ( x ) , z ( x ) y&#x27;(x),z&#x27;(x) 存在,则曲线在P点的切向量 τ = ± ( ( F , G ) ( y , z ) , ( F , G ) ( z , x ) , ( F , G ) ( x , y ) ) \tau=\pm(\frac{\partial(F,G)}{\partial(y,z)},\frac{\partial(F,G)}{\partial(z,x)},\frac{\partial(F,G)}{\partial(x,y)})

空间曲面的切平面与法线

若已知曲面上一点 P ( x 0 , y 0 , z 0 ) P(x_0,y_0,z_0) 处的切平面的法向量为 n = ( A , B , C ) \vec n=(A&#x27;,B&#x27;,C&#x27;) 则曲线在该点的法线方程为 x x 0 A = y y 0 B = z z 0 C \frac{x-x_0}{A&#x27;}=\frac{y-y_0}{B&#x27;}=\frac{z-z_0}{C&#x27;} 切平面方程为 A ( x x 0 ) + B ( y y 0 ) + C ( z z 0 ) = 0 A&#x27;(x-x_0)+B&#x27;(y-y_0)+C&#x27;(z-z_0)=0 当曲面方程为 π : F ( x , y , z ) = 0 \pi:F(x,y,z)=0 在曲面上任取一条过P的曲线,设其方程为 { x = x ( t ) , y = y ( t ) , z = z ( t ) , t [ α , β ] \begin{cases}x=x(t),\\y=y(t),\\z=z(t),\end{cases}t\in[\alpha,\beta] 此时有 F ( x ( t ) , y ( t ) , z ( t ) ) = 0 F(x(t),y(t),z(t))=0 t = t 0 t=t_0 两边对t求导,并写成向量的内积式,得 ( F x , F y , F z ) P ( x ( t 0 ) , y ( t 0 ) , z ( t 0 ) ) = 0 (F_x,F_y,F_z)_P\cdot(x&#x27;(t_0),y&#x27;(t_0),z&#x27;(t_0))=0 则曲线在P点的法向量为 n = ± ( F x , F y , F z ) P \vec{n}=\pm(F_x,F_y,F_z)_P 若曲线由参数方程给出 { x = x ( u , v ) , y = y ( u , v ) , z = z ( u , v ) , \begin{cases}x=x(u,v),\\y=y(u,v),\\z=z(u,v),\end{cases}
则曲线在P点的法向量 n = ± ( ( y , z ) ( u , v ) , ( z , x ) ( u , v ) , ( x , y ) ( u , v ) ) \vec{n}=\pm(\frac{\partial(y,z)}{\partial(u,v)},\frac{\partial(z,x)}{\partial(u,v)},\frac{\partial(x,y)}{\partial(u,v)})

方向导数

设三元函数 u = f ( x , y , z ) u=f(x,y,z) 在点 P 0 ( x 0 , y 0 , z 0 ) P_0(x_0,y_0,z_0) 的某邻域内有定义,任意给定始于点 P 0 P_0 的射线 l l P ( x , y , z ) P(x,y,z) 为l上且含于定义域内的点。若极限 lim r ( p , p 0 ) 0 + f ( P ) f ( P 0 ) r ( P , P 0 ) = lim r ( p , p 0 ) 0 + Δ l f ( P 0 ) r ( P , P 0 ) \lim_{r(p,p_0)\to0^+}\frac{f(P)-f(P_0)}{r(P,P_0)}=\lim_{r(p,p_0)\to0^+}\frac{\Delta_lf(P_0)}{r(P,P_0)} 存在,则称该极限值为函数 f f 在点 P 0 P_0 沿方向 l l 的方向导数,记为 f l P 0 \frac{\partial f}{\partial l}|_{P_0} f ( P 0 ) l \frac{\partial f(P_0)}{\partial l} Δ l f ( P 0 ) r ( P , P 0 ) \frac{\Delta_lf(P_0)}{r(P,P_0)} 称为函数在 P 0 P_0 点沿 l l 方向的增量。特别地, f ( P 0 ) x \frac{\partial f(P_0)}{\partial x} 就是函数在 P 0 P_0 点沿 x x 轴正向的方向导数, y , z y,z 轴上的方向导数同理。若函数在 P 0 P_0 点可微,则其在 P 0 P_0 沿任何方向 l l 的方向导数都存在,则有以下公式 f ( P 0 ) l = ( f x , f y , f z ) P 0 l 0 \frac{\partial f(P_0)}{\partial l}=(\frac{\partial f}{\partial x},\frac{\partial f}{\partial y},\frac{\partial f}{\partial z})|_{P_0}\cdot\vec{l_0} 其中 l 0 = ( cos α , cos β , c o s γ ) = 1 ρ ( Δ x , Δ y , Δ z ) \vec{l_0}=(\cos\alpha,\cos\beta,cos\gamma)=\frac{1}{\rho}(\Delta x,\Delta y,\Delta z) l l 的方向余弦。

泰勒公式

f ( n ) ( x ) f^{(n)}(x) 表示f(x)的n阶导数。
只要让余项<EPS即可计算指定函数到任意精确度。
特别取a=0时称为麦克劳林公式。
f ( x ) = f ( a ) + f ( 1 ) ( a ) ( x a ) + f ( 2 ) ( a ) 2 ! ( x a ) 2 + + f ( n ) ( a ) n ! ( x a ) n + R n ( x ) f(x)=f(a)+f^{(1)}(a)(x-a)+\frac{f^{(2)}(a)}{2!}(x-a)^2+\ldots+\frac{f^{(n)}(a)}{n!}(x-a)^n+R_n(x)
R n ( x ) = o ( ( x a ) n ) R_n(x)=o((x-a)^n) ,佩亚诺余项
R n ( x ) = 1 n ! a x ( x t ) n f ( n + 1 ) ( t ) &ThinSpace; d t R_n(x)=\frac{1}{n!}\int_a^x(x-t)^nf^{(n+1)}(t)\,dt ,积分余项
R n ( x ) = f ( n + 1 ) ( ξ ) ( n + 1 ) ! ( x a ) n + 1 , a &lt; ξ &lt; x R_n(x)=\frac{f^{(n+1)}(\xi)}{(n+1)!}(x-a)^{n+1},a&lt;\xi&lt;x ,拉格朗日余项
R n ( x ) = ( x a ) n + 1 n ! ( 1 θ ) n f ( n + 1 ) ( a + θ ( x a ) ) , 0 &lt; θ &lt; 1 R_n(x)=\frac{(x-a)^{n+1}}{n!}(1-\theta)^nf^{(n+1)}(a+\theta(x-a)),0&lt;\theta&lt;1 ,柯西余项

指数函数

( e x ) ( n ) = e x (e^x)^{(n)}=e^x
e x = 1 + x + x 2 2 ! + x 3 3 ! + + x n n ! + R n ( x ) e^x=1+x+\frac{x^2}{2!}+\frac{x^3}{3!}+\ldots+\frac{x^n}{n!}+R_n(x)
R n ( x ) = e θ x ( n + 1 ) ! x n + 1 , ξ = θ x , 0 &lt; θ &lt; 1 R_n(x)=\frac{e^{\theta x}}{(n+1)!}x^{n+1},\xi=\theta x,0&lt;\theta&lt;1

三角函数

( sin x ) ( n ) = sin ( x + n π 2 ) (\sin x)^{(n)}=\sin(x+\frac{n\pi}{2})
sin x = x x 3 3 ! + x 5 5 ! x 7 7 ! + + ( 1 ) k 1 x 2 k 1 ( 2 k 1 ) ! + R 2 k ( x ) \sin x=x-\frac{x^3}{3!}+\frac{x^5}{5!}-\frac{x^7}{7!}+\ldots+(-1)^{k-1}\frac{x^{2k-1}}{(2k-1)!}+R_{2k}(x)
R 2 k ( x ) = ( 1 ) k cos θ x ( 2 k + 1 ) ! x 2 k + 1 R_{2k}(x)=(-1)^k\frac{\cos\theta x}{(2k+1)!}x^{2k+1}
( cos x ) ( n ) = cos ( x + n π 2 ) (\cos x)^{(n)}=\cos(x+\frac{n\pi}{2})
cos x = 1 x 2 2 ! + x 4 4 ! x 6 6 ! + + ( 1 ) k 1 x 2 k 2 ( 2 k 2 ) ! + R 2 k 1 ( x ) \cos x=1-\frac{x^2}{2!}+\frac{x^4}{4!}-\frac{x^6}{6!}+\ldots+(-1)^{k-1}\frac{x^{2k-2}}{(2k-2)!}+R_{2k-1}(x)
R 2 k 1 ( x ) = ( 1 ) k cos θ x ( 2 k ) ! x 2 k R_{2k-1}(x)=(-1)^k\frac{\cos\theta x}{(2k)!}x^{2k}

对数函数

[ ln ( 1 + x ) ] ( n ) = ( 1 ) n 1 ( n 1 ) ! ( 1 + x ) n [\ln(1+x)]^{(n)}=(-1)^{n-1}(n-1)!(1+x)^{-n}
ln ( 1 + x ) = x x 2 2 + x 3 3 x 4 4 + + ( 1 ) n 1 x n n + R n ( x ) \ln(1+x)=x-\frac{x^2}{2}+\frac{x^3}{3}-\frac{x^4}{4}+\ldots+(-1)^{n-1}\frac{x^n}{n}+R_n(x)

幂函数

[ ( 1 + x ) a ] ( n ) = a ( a 1 ) ( a n + 1 ) ( 1 + x ) a n [(1+x)^a]^{(n)}=a(a-1)\ldots(a-n+1)(1+x)^{a-n}
( 1 + x ) a = 1 + a x + a ( a 1 ) 2 ! x 2 + + a ( a 1 ) ( a n + 1 ) n ! x n + R n ( x ) (1+x)^a=1+ax+\frac{a(a-1)}{2!}x^2+\dots+\frac{a(a-1)\ldots(a-n+1)}{n!}x^n+R_n(x)

二元函数

f ( x , y ) f(x,y) P 0 ( x 0 , y 0 ) P_0(x_0,y_0) 的某邻域 O ( P 0 ) O(P_0) 内有直到 n + 1 n+1 阶连续偏导数,则对 O ( P 0 ) O(P_0) ( x 0 + Δ x , y 0 + Δ y ) , θ ( 0 , 1 ) \forall(x_0+\Delta x,y_0+\Delta y),\exist\theta\in(0,1) ,使得 f ( x 0 + Δ x , y 0 + Δ y ) = k = 0 n 1 k ! ( x Δ x + y Δ y ) k f ( x 0 , y 0 ) + R n f(x_0+\Delta x,y_0+\Delta y)=\sum_{k=0}^n\frac{1}{k!}(\frac{\partial}{\partial x}\Delta x+\frac{\partial}{\partial y}\Delta y)^kf(x_0,y_0)+R_n 其中 R n = 1 ( n + 1 ) ! ( x Δ x + y Δ y ) n + 1 f ( x 0 + θ Δ x , y 0 + θ Δ y ) R_n=\frac{1}{(n+1)!}(\frac{\partial}{\partial x}\Delta x+\frac{\partial}{\partial y}\Delta y)^{n+1}f(x_0+\theta\Delta x,y_0+\theta\Delta y)

级数部分和

调和级数

n , i = 1 n 1 i ln n + r , r 0.5772156649015328 n\to\infty,\sum_{i=1}^n\frac 1 i\to\ln n+r,r\approx0.5772156649015328\ldots

幂级数

快速计算幂级数的部分和 i = 1 n i k m o d &ThinSpace;&ThinSpace; M \sum_{i=1}^ni^k\mod M 可借助伯努利数,详见模板·组合数学
i = 1 n i 1 = 1 2 n ( n + 1 ) \sum_{i=1}^ni^1=\frac 1 2n(n+1)
i = 1 n i 2 = 1 6 n ( n + 1 ) ( 2 n + 1 ) \sum_{i=1}^ni^2=\frac 1 6n(n+1)(2n+1)
i = 1 n i 3 = 1 4 [ n ( n + 1 ) ] 2 \sum_{i=1}^ni^3=\frac 1 4[n(n+1)]^2
i = 1 n i 4 = 1 30 n ( n + 1 ) ( 2 n + 1 ) ( 3 n 2 + 3 n 1 ) \sum_{i=1}^ni^4=\frac 1{30}n(n+1)(2n+1)(3n^2+3n-1)
i = 1 n i 5 = 1 12 [ n ( n + 1 ) ] 2 ( 2 n 2 + 2 n 1 ) \sum_{i=1}^ni^5=\frac 1{12}[n(n+1)]^2(2n^2+2n-1)
i = 1 n i 6 = 1 42 n ( n + 1 ) ( 2 n + 1 ) ( 3 n 4 + 6 n 3 3 n + 1 ) \sum_{i=1}^ni^6=\frac 1{42}n(n+1)(2n+1)(3n^4+6n^3-3n+1)

二分求零点、三分求极值点

需要 f ( x ) f(x) 在区间 [ l , r ] [l,r] 上单调/凹凸性唯一。

double bs(double l,double r,double f(double x))
{
 if(r-l<EPS)return l;
 double m=(l+r)/2;
 return sgn(f(l)*f(m))<0?bs(l,m,f):ts(m,r,f);
}
double ts(double l,double r,double f(double x))
{
 if(r-l<EPS)return l;
 double d=(r-l)/3,lm=l+d,rm=r-d;
 return f(lm)<f(rm)?ts(l,rm,f):ts(lm,r,f);//极小值
}

插值法

拉格朗日插值法:插值多项式和插值基函数的形式对称,容易编程。但是,增加节点时,需要重新计算每一个插值基函数。要在 ( m o d p ) \pmod p 意义下进行的话,那么p只能是质数。
牛顿插值法:当插值节点增加时,之前已计算的结果仍然能用,每增加一个节点,只要再增加一项即可,从而避免了重复性计算。如果要mod非质数的话,那么就要用牛顿插值法。

typedef complex<double> Coord;
#define X real()
#define Y imag()
double lagrange(const vector<Coord> &p,double x)//返回p确定的多项式函数在x处的值
{
	double ret=0;
	for(int i=0; i<p.size(); ++i)
	{
		double tmp=p[i].Y;
		for(int j=0; j<p.size(); ++j)
			if(i!=j)tmp*=(x-p[j].X)/(p[i].X-p[j].X);
		ret+=tmp;
	}
	return ret;
}
vector<double> lagrange(vector<Coord> p)//返回p确定的多项式系数向量
{
	vector<double> ret(p.size()),sum(p.size());
	ret[0]=p[0].Y,sum[0]=1;
	for(int i=1; i<p.size(); ++i)
	{
		for(int j=p.size()-1; j>=i; --j)
			p[j].Y=(p[j].Y-p[j-1].Y)/(p[j].X-p[j-i].X);
		for(int j=i; ~j; --j)
			sum[j]=(j?sum[j-1]:0)-sum[j]*p[i-1].X,
			       ret[j]+=sum[j]*p[i].Y;
	}
	return ret;
}
double differenceQuotient(const vector<Coord> &p,int k)//计算差商
{
	double ret=0;
	for(int i=0; i<=k; ++i)
	{
		double tmp=p[i].Y;
		for(int j=0; j<=k; ++j)
			if(i!=j)tmp/=p[i].X-p[j].X;
		ret+=tmp;
	}
	return ret;
}
double newton(const vector<Coord> &p,double x)
{
	double ret=p[0].Y;
	for(int i=1; i<p.size(); ++i)
	{
		double tmp=differenceQuotient(p,i);//多次求,可O(n^3)预处理优化
		for(int j=0; j<i; ++j)tmp*=x-p[j].X;
		ret+=tmp;
	}
	return ret;
}

计算几何

二维

点和向量化为坐标Coord进行运算,使用stl中的complex实现。
复数相乘的几何意义为长度相乘,极角相加。
用直线上的一点p和方向向量v表示一条经过p的直线,直线上的所有点q满足q=p+t*v,其中t是参数;当限制t≥0时,该参数方程表示射线;限制0≤t≤1时,该参数方程表示线段。
此外,如果已知线段端点a1和a2,可以通过Line(a1,a2-a1)来得到对应的参数形式。
Morley定理:三角形每个内角的三等分线相交成等边三角形。
欧拉定理:平面图的点数V、边数E和面数F满足V+F-E=2。

typedef double lf;
typedef complex<lf> Coord;
const lf EPS=1e-9,PI=acos(-1);
#define X real()
#define Y imag()
struct Line
{
	Coord p,v;
	Line(Coord p=Coord(),Coord v=Coord()):p(p),v(v) {}
	Coord point(lf t)
	{
		return p+v*t;
	}
};
struct Circle
{
	Coord c;
	lf r;
	Circle(Coord c=Coord(),lf r=0):c(c),r(r) {}
	Coord point(lf t)//t为参数,幅角
	{
		return c+polar(r,t);
	}
};
/*
Coord(lf x=0,lf y=0);//构造函数
lf real(Coord a);//a的实部(复平面的横坐标),也可写作a.real()
lf imag(Coord a);//a的虚部(复平面的纵坐标),也可写作a.imag()

lf abs(Coord a);//向量a的模长,或是点a到原点的距离
lf norm(Coord a);//abs的平方,比abs快,但是要注意浮点数精度溢出

lf arg(Coord a);//a的幅角,与atan2(a.real(),a.imag())等价
Coord polar(lf r,lf t);//极坐标生成方式,r为幅值,t为幅角

//运算符重载+、-、*、/(以及对应的赋值运算,但是赋值运算不能写在表达式中,详见参考地址)、<<、>>(输出括号形式的坐标)
*/
int sgn(lf d)
{
	return (d>EPS)-(d<-EPS);
}

bool operator!=(const Coord &A,const Coord &B)//不等运算符,涉及到浮点数比较要重写
{
	return sgn(A.X-B.X)||sgn(A.Y-B.Y);
}

bool operator==(const Coord &A,const Coord &B)
{
	return !(A!=B);
}

bool cmpCoord(const Coord &A,const Coord &B)//复数没有小于运算,只能这样定义一个比较函数
{
	return sgn(A.X-B.X)?
	       A.X<B.X:
	       A.Y+EPS<B.Y;
}

bool cmpLine(const Line &A,const Line &B)//按极角排序,求凸包中使用
{
	return arg(A.v)<arg(B.v);
}

lf Dot(Coord A,Coord B)
{
	return A.X*B.X+A.Y*B.Y;
}

lf Cross(Coord A,Coord B)
{
	return A.X*B.Y-B.X*A.Y;
}

lf Angle(Coord A,Coord B)
{
	return acos(Dot(A,B)/abs(A)/abs(B));
}

lf Area2(Coord A,Coord B,Coord C)//三角形ABC有向面积的两倍
{
	return Cross(B-A,C-A);
}

Coord Rotate(Coord A,lf rad)//向量A逆时针旋转rad弧度
{
	return A*polar(1.0,rad);
}

Coord Normal(Coord A)//A的法向量,把A逆时针旋转九十度并长度化为1
{
	lf L=abs(A);
	return Coord(-A.Y/L,A.X/L);
}

bool onLeft(Coord P,Line L)//p是否在有向直线L左侧,不含线上
{
	return Cross(L.v,P-L.p)>0;
}

lf DistanceToLine(Coord P,Line L)//点到直线距离(有向)
{
	return Cross(L.v,P-L.p)/abs(L.v);
}

lf DistanceToLine(Coord P,Coord A,Coord B)
{
	return DistanceToLine(P,Line(A,B-A));
}

lf DistanceToSegment(Coord P,Coord A,Coord B)//点到线段的距离(无向)
{
	if(A==B)return abs(P-A);
	Coord v1=B-A,v2=P-A,v3=P-B;
	if(sgn(Dot(v1,v2))<0)return abs(v2);
	if(sgn(Dot(v1,v3))>0)return abs(v3);
	return fabs(DistanceToLine(P,Line(A,B-A)));
}

Coord getLineProjection(Coord P,Line L)//点在直线上的投影
{
	return L.point(Dot(L.v,P-L.p)/norm(L.v));
}

Coord getLineProjection(Coord P,Coord A,Coord B)
{
	return getLineProjection(P,Line(A,B-A));
}

Coord getSymmetry(Coord P,Coord O)//P关于O的对称点
{
	return O+O-P;
}

Coord getSymmetry(Coord P,Line L)//P关于L的对称点
{
	return getSymmetry(P,getLineProjection(P,L));
}

Coord getLineIntersection(Line L1,Line L2)//直线交点,须确保两直线相交
{
	return L1.point(Cross(L2.v,L1.p-L2.p)/Cross(L1.v,L2.v));
}

Coord getLineIntersection(Coord A1,Coord A2,Coord B1,Coord B2)
{
	return getLineIntersection(Line(A1,A2-A1),Line(B1,B2-B1));
}

bool SegmentProperIntersection(Coord A1,Coord A2,Coord B1,Coord B2)//线段相交判定,交点不在一条线段的端点
{
	lf C1=Cross(A2-A1,B1-A1),C2=Cross(A2-A1,B2-A1),
	   C3=Cross(B2-B1,A1-B1),C4=Cross(B2-B1,A2-B1);
	return sgn(C1)*sgn(C2)<0&&sgn(C3)*sgn(C4)<0;
}

bool onSegment(Coord P,Coord A1,Coord A2)//判断点是否在线段上,不包含端点
{
	return sgn(Dot(A1-P,A2-P))<0&&!sgn(Cross(A1-P,A2-P));
}

lf PolygonArea(const vector<Coord> &p)//计算多边形的有向面积,凸多边形即为面积
{
	lf area=0;
	for(int i=1,n=p.size()-1; i<n; ++i)
		area+=Area2(p[0],p[i],p[i+1]);
	return area/2;
}

int inPolygon(Coord p,const vector<Coord> &poly)//点在多边形内的判定,转角法,正值为内部,0为外部,-1在边界上
{
	int ans=0;
	for(int i=0,k,d1,d2,n=poly.size(); i!=n; ++i)
	{
		if(onSegment(p,poly[i],poly[(i+1)%n]))return -1;//在边界上
		k=sgn(Cross(poly[(i+1)%n]-poly[i],p-poly[i]));
		d1=sgn(poly[i].Y-p.Y);
		d2=sgn(poly[(i+1)%n].Y-p.Y);
		if(k>0&&d1<=0&&d2>0)++ans;
		if(k<0&&d2<=0&&d1>0)++ans;
	}
	return ans;
}

int ConvexHull(vector<Coord> p,vector<Coord> &sol)//获得凸包;不希望凸包的边上有输入点,把两个<=改成<
{
	sort(p.begin(),p.end(),cmpCoord);//先比横坐标再比纵坐标
	for(int i=0; i!=p.size(); ++i)
	{
		while(sol.size()>1&&
		        Area2(sol[sol.size()-2],sol[sol.size()-1],p[i])<=0)
			sol.pop_back();
		sol.push_back(p[i]);
	}
	for(int i=sol.size()-2,k=sol.size(); i>=0; --i)
	{
		while(sol.size()>k&&
		        Area2(sol[sol.size()-2],sol[sol.size()-1],p[i])<=0)
			sol.pop_back();
		sol.push_back(p[i]);
	}
	if(p.size()>1)sol.pop_back();
	return sol.size();
}

vector<Coord> cutPolygon(const vector<Coord> &poly,Coord A,Coord B)//用有向直线A->B切割多边形poly, 返回“左侧”。 如果退化,可能会返回一个单点或者线段,复杂度O(n^2)
{
	vector<Coord> newpoly;
	for(int i=0,n=poly.size(); i!=n; ++i)
	{
		Coord C=poly[i],D=poly[(i+1)%n];
		if(sgn(Cross(B-A,C-A))>=0)newpoly.push_back(C);
		if(!sgn(Cross(B-A, C-D)))
		{
			Coord ip=getLineIntersection(Line(A,B-A),Line(C,D-C));
			if(onSegment(ip,C,D))newpoly.push_back(ip);
		}
	}
	return newpoly;
}

vector<Coord> getHalfPlaneIntersection(vector<Line> &L)//半平面交
{
	sort(L.begin(),L.end(),cmpLine);//按极角排序

	int first,last;//双端队列的第一个元素和最后一个元素
	vector<Coord> p(L.size(),Coord()); //p[i]为q[i]和q[i+1]的交点
	vector<Line> q(L.size(),Line());//双端队列
	q[first=last=0]=L[0]; //队列初始化为只有一个半平面L[0]

	for(int i=0,n=L.size(); i!=n; ++i)
	{
		while(first<last&&!onLeft(p[last-1],L[i]))
			--last;
		while(first<last&&!onLeft(p[first],L[i]))
			++first;

		q[++last]=L[i];
		if(!sgn(Cross(q[last].v,q[last-1].v)))
		{
			--last;
			if(onLeft(L[i].p,q[last]))
				q[last]=L[i];
		}
		if(first<last)
			p[last-1]=getLineIntersection(q[last-1], q[last]);
	}
	while(first<last&&!onLeft(p[last-1],q[first]))
		--last;//删除无用平面

	if(last-first<=1)return vector<Coord>();//空集
	p[last]=getLineIntersection(q[last],q[first]);
	return vector<Coord>(p.begin()+first,p.begin()+last+1);//从deque复制到输出中
}

int getLineCircleIntersection(Line L,Circle C,vector<Coord> &sol)
{
	lf a=L.v.X,
	   b=L.p.X-C.c.X,
	   c=L.v.Y,
	   d=L.p.Y-C.c.Y,
	   e=a*a+c*c,
	   f=2*(a*b+c*d),
	   g=b*b+d*d-C.r*C.r,
	   delta=f*f-4*e*g;

	if(sgn(delta)<0)return 0;
	if(!sgn(delta))
		return sol.push_back(L.point(-f/(2*e))),1;
	sol.push_back(L.point((-f-sqrt(delta))/(2*e)));
	sol.push_back(L.point((-f+sqrt(delta))/(2*e)));
	return 2;
}

int getCircleIntersection(Circle C1,Circle C2,vector<Coord> &sol)
{
	lf d=abs(C1.c-C2.c);

	if(!sgn(d))
		return sgn(C1.r-C2.r)?0:-1;//重合返回-1

	if(sgn(C1.r+C2.r-d)<0||sgn(fabs(C1.r-C2.r)-d)>0)//外离或内含
		return 0;

	lf a=arg(C2.c-C1.c),
	   da=acos((C1.r*C1.r+d*d-C2.r*C2.r)/(2*C1.r*d));

	Coord p1=C1.point(a-da),p2=C1.point(a+da);

	sol.push_back(p1);

	if(p1==p2)return 1;//相切
	return sol.push_back(p2),2;
}

Line getTangent(Coord C,Coord P)//圆心C,圆上一点P处切线
{
	return Line(P,Normal(C-P));
}

int getTangents(Coord p,Circle C,vector<Coord> &sol)//点到圆的切点,返回个数
{
	Coord u=p-C.c;
	lf d=abs(u);
	if(d<C.r)return 0;//点在圆内
	if(!sgn(d-C.r))//点在圆上
		return sol.push_back(p),1;
	lf base=arg(u),ang=acos(C.r/d);
	sol.push_back(C.point(base+ang));
	sol.push_back(C.point(base-ang));
	return 2;
}

int getTangents(Circle A,Circle B,vector<Coord> &a,vector<Coord> &b)//公共切线的切点
{
	int cnt=0;

	if(A.r<B.r)
		swap(A,B),swap(a,b);//有时需标记交换

	lf d=abs(A.c-B.c),
	   rdiff=A.r-B.r,
	   rsum=A.r+B.r;

	if(sgn(d-rdiff)<0)return 0;//内含

	lf base=arg(B.c-A.c);

	if(!sgn(d)&&!sgn(rdiff))return -1;//重合,无穷多条切线

	if(!sgn(d-rdiff))//内切,外公切线
	{
		a.push_back(A.point(base));
		b.push_back(B.point(base));
		return 1;
	}

	//有外公切线的情形
	lf ang=acos(rdiff/d);
	a.push_back(A.point(base+ang));
	b.push_back(B.point(base+ang));
	a.push_back(A.point(base-ang));
	b.push_back(B.point(base-ang));
	cnt+=2;

	if(!sgn(d-rsum))
	{
		a.push_back(A.point(base));
		b.push_back(B.point(base+PI));
		++cnt;
	}
	else if(sgn(d-rsum)>0)
	{
		lf ang_in=acos(rsum/d);
		a.push_back(A.point(base+ang_in));
		b.push_back(B.point(base+ang_in+PI));
		a.push_back(A.point(base-ang_in));
		b.push_back(B.point(base-ang_in+PI));
		cnt+=2;
	}
	return cnt;
}

lf AreaCircleWithTriangle(Circle C,Coord A,Coord B)//C和三角形OAB的相交面积,如果三角形顶点不在O上则把圆和三角形同时平移,直到有一个顶点在O上
{
	int sg=sgn(Cross(A,B));
	if(!sg||A==C.c||B==C.c)return 0;

	lf OA=abs(A-C.c),OB=abs(B-C.c),angle=Angle(A,B),
	   d=DistanceToLine(Coord(),A,B);

	if(sgn(OA-C.r)<=0&&sgn(OB-C.r)<=0)
		return Cross(A,B)/2;

	if(sgn(OA-C.r)>=0&&sgn(OB-C.r)>=0&&sgn(d-C.r)>=0)
		return sg*C.r*C.r*angle/2;
	if(sgn(OA-C.r)>=0&&sgn(OB-C.r)>=0&&sgn(d-C.r)<0)
	{
		Coord prj=getLineProjection(Coord(),A,B);
		if(!onSegment(prj,A,B))return sg*C.r*C.r*angle/2;

		vector<Coord> p;
		Line L=Line(A,B-A);
		getLineCircleIntersection(L,C,p);

		lf s1=C.r*C.r*angle/2,
		   s2=C.r*C.r*Angle(p[0],p[1])/2;
		s2-=fabs(Cross(p[0],p[1])/2);
		s1=s1-s2;

		return sg*s1;
	}
	if(sgn(OB-C.r)<0)swap(A,B);

	Line L=Line(A,B-A);
	vector<Coord> inter;
	getLineCircleIntersection(L,C,inter);
	Coord inter_point=inter[!onSegment(inter[0],A,B)];

	lf s=fabs(Cross(inter_point,A)/2);
	s+=C.r*C.r*Angle(inter_point,B)/2;

	return s*sg;
}

lf AreaCircleWithPolygon(Circle C,const vector<Coord> &p)
{
	lf ans=0;
	for(int i=0; i<p.size(); ++i)
		ans+=AreaCircleWithTriangle(C,p[i],p[(i+1)%p.size()]);
	return fabs(ans);
}

Coord getGravityCenter(const vector<Coord> &p)//多边形重心
{
	Coord a(0,0);
	lf am=0,mj;
	for(int i=0; i<p.size(); ++i)
	{
		mj=Cross(p[i],p[(i+1)%p.size()]);
		a+=mj*(p[i]+p[(i+1)%p.size()]);
		am+=mj;
	}
	return a/am/3.0;
}

struct Area//扫描线求各边平行于坐标轴的矩形面积交并
{
	struct Seg
	{
		ll l,r,h;
		int f;//矩形上边缘或下边缘
		bool operator<(const Seg &s)const
		{
			return h<s.h;
		}
	};
	vector<Seg> s;
	Ranker rk;
	void add(ll l,ll b,ll r,ll t)//(l,b)~(r,t)
	{
		rk.push_back(l),rk.push_back(r);
		s.push_back({l,r,b,1}),s.push_back({l,r,t,0});
	}
	ll ask(int k)//返回重叠层数至少为k的面积
	{
		ll ans=0,sum=0;
		sort(s.begin(),s.end());
		rk.init();
		vector<int> vis(rk.size(),0);
		for(int i=0; i+1<s.size(); ++i)
		{
			for(int j=rk.ask(s[i].l),e=rk.ask(s[i].r); j<e; ++j)//此处可线段树维护
			{
				if(s[i].f&&++vis[j]==k)sum+=rk[j+1]-rk[j];
				if(!s[i].f&&vis[j]--==k)sum-=rk[j+1]-rk[j];
			}
			ans+=sum*(s[i+1].h-s[i].h);
		}
		return ans;
	}
};

三维

typedef double lf;
const lf EPS=1e-9,INF=1e9;
struct Coord3
{
	lf X,Y,Z;
	Coord3(lf X=0,lf Y=0,lf Z=0):X(X),Y(Y),Z(Z) {}
};

int sgn(lf d)
{
	return (d>EPS)-(d<-EPS);
}
bool operator!=(const Coord3 &a,const Coord3 &b)
{
	return sgn(a.X-b.X)||sgn(a.Y-b.Y)||sgn(a.Z-b.Z);
}
bool operator==(const Coord3 &a,const Coord3 &b)
{
	return !(a!=b);
}

Coord3& operator+=(Coord3 &a,const Coord3 &b)
{
	return a.X+=b.X,a.Y+=b.Y,a.Z+=b.Z,a;
}
Coord3 operator+(Coord3 a,const Coord3 &b)
{
	return a+=b;
}

Coord3& operator-=(Coord3 &a,const Coord3 &b)
{
	return a.X-=b.X,a.Y-=b.Y,a.Z-=b.Z,a;
}
Coord3 operator-(Coord3 a,const Coord3 &b)
{
	return a-=b;
}

Coord3& operator*=(Coord3 &a,lf d)
{
	return a.X*=d,a.Y*=d,a.Z*=d,a;
}
Coord3 operator*(Coord3 a,lf d)
{
	return a*=d;
}
Coord3 operator*(lf d,Coord3 a)
{
	return a*=d;
}

Coord3& operator/=(Coord3 &a,lf d)
{
	return a.X/=d,a.Y/=d,a.Z/=d,a;
}
Coord3 operator/(Coord3 a,lf d)
{
	return a/=d;
}

lf Dot(const Coord3& A,const Coord3& B)
{
	return A.X*B.X+A.Y*B.Y+A.Z*B.Z;
}

Coord3 Cross(const Coord3& A,const Coord3& B)
{
	return Coord3(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 norm(const Coord3& A)
{
	return Dot(A,A);
}

lf abs(const Coord3& A)
{
	return sqrt(norm(A));
}

lf Angle(const Coord3& A,const Coord3& B)
{
	return acos(Dot(A,B)/abs(A)/abs(B));
}

lf Area2(Coord3 A,Coord3 B,Coord3 C)
{
	return abs(Cross(B-A,C-A));
}

lf Volume6(Coord3 A,Coord3 B,Coord3 C,Coord3 D)//四面体体积
{
	return Dot(D-A,Cross(B-A,C-A));
}

Coord3 Centroid(Coord3 A,Coord3 B,Coord3 C,Coord3 D)//四面体的重心
{
	return (A+B+C+D)/4.0;
}

lf DistanceToPlane(Coord3 p,Coord3 p0,const Coord3& n)//点p到平面p0-n的有向距离
{
	return Dot(p-p0,n)/abs(n);
}

Coord3 getPlaneProjection(Coord3 p,Coord3 p0,const Coord3& n)//点p在平面p0-n上的投影。n必须为单位向量
{
	return p-n*Dot(p-p0,n);
}

Coord3 LinePlaneIntersection(Coord3 p1,Coord3 p2,Coord3 p0,Coord3 n)//直线p1-p2 与平面p0-n的交点,假设交点唯一存在
{
	Coord3 v=p2-p1;
	lf t=Dot(n,p0-p1)/Dot(n,p2-p1);//分母为0,直线与平面平行或在平面上
	return p1+v*t;//如果是线段 判断t是否在0~1之间
}

lf DistanceToLine(Coord3 P,Coord3 A,Coord3 B)//点P到直线AB的距离
{
	Coord3 v1=B-A,v2=P-A;
	return abs(Cross(v1,v2))/abs(v1);
}

lf DistanceToSeg(Coord3 P,Coord3 A,Coord3 B)//点到线段的距离
{
	if(A==B)return abs(P-A);
	Coord3 v1=B-A,v2=P-A,v3=P-B;
	if(sgn(Dot(v1,v2))<0)return abs(v2);
	if(sgn(Dot(v1,v3))>0)return abs(v3);
	return fabs(DistanceToLine(P,A,B));
}

bool LineDistance3D(Coord3 p1,Coord3 u,Coord3 p2,Coord3 v,lf& s)//求异面直线 p1+s*u与p2+t*v的公垂线对应的s,如果平行|重合,返回0
{
	lf b=Dot(u,u)*Dot(v,v)-Dot(u,v)*Dot(u,v);
	if(!sgn(b))return 0;
	lf a=Dot(u,v)*Dot(v,p1-p2)-Dot(v,v)*Dot(u,p1-p2);
	return s=a/b,1;
}

bool SameSide(Coord3 p1,Coord3 p2,Coord3 a,Coord3 b)//p1和p2是否在线段a-b的同侧
{
	return sgn(Dot(Cross(b-a,p1-a),Cross(b-a,p2-a)))>=0;
}

bool PointInTri(Coord3 PP,Coord3 P[3])//点P在三角形P0,P1,p中
{
	return SameSide(PP,P[0],P[1],P[2])&&
	       SameSide(PP,P[1],P[0],P[2])&&
	       SameSide(PP,P[2],P[0],P[1]);
}

bool TriSegIntersection(Coord3 P[3],Coord3 A,Coord3 B,Coord3& PP)//三角形P0P1p是否和线段AB相交,如有则为PP 
{
	Coord3 n=Cross(P[1]-P[0],P[2]-P[0]);
	if(sgn(Dot(n,B-A))==0)return false;//线段A-B和平面P0P1p平行或共面
	lf t=Dot(n,P[0]-A)/Dot(n,B-A);//平面A和直线P1-p有惟一交点
	if(sgn(t)<0||sgn(t-1)>0)return false;//不在线段AB上
	return PointInTri(PP=A+(B-A)*t,P);
}

bool TriTriIntersection(Coord3 T1[3],Coord3 T2[3])//空间两三角形是否相交
{
	Coord3 P;
	for(int i=0; i<3; ++i)
		if(TriSegIntersection(T1,T2[i],T2[(i+1)%3],P)||
		        TriSegIntersection(T2,T1[i],T1[(i+1)%3],P))
			return 1;
	return 0;
}

lf SegSegDistance(Coord3 a1,Coord3 b1,Coord3 a2,Coord3 b2,Coord3 &ans1,Coord3 &ans2)//空间两直线上最近点对 返回最近距离 点对保存在ans1 ans2中
{
	Coord3 v1=(a1-b1),v2=(a2-b2);
	Coord3 N=Cross(v1,v2);
	Coord3 ab=(a1-a2);
	lf ans=Dot(N,ab)/abs(N);
	Coord3 d1=b1-a1,d2=b2-a2,cd=Cross(d1,d2);
	lf nd=norm(cd),t1=Dot(Cross(a2-a1,d2),cd)/nd,t2=Dot(Cross(a2-a1,d1),cd)/nd;
	return ans1=a1+(b1-a1)*t1,ans2=a2+(b2-a2)*t2,fabs(ans);
}

bool InsideWithMinDistance(Coord3 PP,Coord3 *P,lf dist)//判断PP是否在三角形P中,并且到三条边的距离都至少为dist。保证P,A,B,C共面
{
	return PointInTri(PP,P)&&
	       DistanceToLine(PP,P[0],P[1])>=dist||
	       DistanceToLine(PP,P[1],P[2])>=dist||
	       DistanceToLine(PP,P[2],P[0])>=dist;
}

struct ConvexPolyhedron//空间多边形和凸包问题
{
	struct Face
	{
		int v[3];
		Face(int a,int b,int c)
		{
			v[0]=a,v[1]=b,v[2]=c;
		}
		Coord3 Normal(const vector<Coord3>& P)const
		{
			return Cross(P[v[1]]-P[v[0]],P[v[2]]-P[v[0]]);
		}
		bool CanSee(const vector<Coord3>& P,int i)const//f是否能看见P[i]
		{
			return Dot(P[i]-P[v[0]],Normal(P))> 0;
		}
	};
	vector<Face> faces;
	vector<Coord3> p;
	ConvexPolyhedron(vector<Coord3> P):p(P)
	{
		for(int i=0; i<p.size(); ++i)P[i]+=Coord3(randEPS(),randEPS(),randEPS());
		vector<vector<int> > vis(P.size(),vector<int>(P.size()));
		faces.push_back(Face(0,1,2));//由于已经进行扰动,前三个点不共线
		faces.push_back(Face(2,1,0));
		for(int i=3; i<P.size(); ++i)
		{
			vector<Face> next;
			for(int j=0; j<faces.size(); ++j)//计算每条边的“左面”的可见性
			{
				Face& f=faces[j];
				int res=f.CanSee(P,i);
				if(!res)next.push_back(f);
				for(int k=0; k<3; ++k)vis[f.v[k]][f.v[(k+1)%3]]=res;
			}
			for(int j=0; j<faces.size(); ++j)
				for(int k=0; k<3; ++k)
				{
					int a=faces[j].v[k],b=faces[j].v[(k+1)%3];
					if(vis[a][b]!=vis[b][a]&&vis[a][b])//(a,b)是分界线,左边对P[i]可见
						next.push_back(Face(a,b,i));
				}
			swap(faces,next);
		}
	}
	lf randEPS()
	{
		return (rand()/lf(RAND_MAX)-0.5)*EPS;
	}
	Coord3 centroid()//三维凸包重心
	{
		Coord3 C=p[0],tot(0,0,0);
		lf totv=0;
		for(int i=0; i<faces.size(); ++i)
		{
			Coord3 p1=p[faces[i].v[0]],p2=p[faces[i].v[1]],p3=p[faces[i].v[2]];
			lf v=-Volume6(p1,p2,p3,C);
			totv+=v;
			tot+=v*Centroid(p1,p2,p3,C);
		}
		return tot/totv;
	}
	lf dist(Coord3 C)//凸包内一点到表面最近距离
	{
		lf ans=INF;
		for(int i=0; i<faces.size(); ++i)
		{
			Coord3 p1=p[faces[i].v[0]],p2=p[faces[i].v[1]],p3=p[faces[i].v[2]];
			ans=min(ans,fabs(-Volume6(p1,p2,p3,C)/Area2(p1,p2,p3)));
		}
		return ans;
	}
};

高精度

bitset

代替整型进行位运算,更方便并且可以处理超过最大整形范围大小的位集合。
你可以把bitset看作可以位运算的bool数组,换言之,bitset的大小是固定的。因此,用bitset做状态压缩是很方便的,也可以方便的实现集合的交并补操作。
bitset仅重载了相等不等和位运算符,原生不支持四则运算和大小比较,所以很少代替高精度数。

bitset<N> b(unsigned long long u=0);//用u的低N位初始化b,N是一个可转成ULL类型的常量表达式,高位补0
bitset<N> b(string s,int pos,int m=string::npos,char zero='0',char one='1');//用s从pos位开始的m位初始化b,s中只含zero和one

b.size();//b的大小,即N
b.count();//b中1的个数

b[pos];//b中pos位的引用

b.set();//b全赋1
b.reset();//b全赋0
b.flip();//b全反转

b.to_ull();//b转成unsigned long long,b.size()>64时出错
b.to_string(char zero='0',char one='1');//按参数输出字符串

os<<b;//按'0'和'1'打印b
is>>b;//按'0'和'1'读入b,当下一个字符不是'0'或'1'或读到b.size()个数后停止

==、!=、<<、>>、&、|、^//保持它们在整型运算中的含义

大小比较

其他运算符类似。

typedef bitset<127> Bint;
bool operator<(const Bint &a,const Bint &b)
{
	for(int i=a.size()-1; ~i; --i)
		if(a[i]<b[i])return 1;
	return 0;
}
bool operator!=(const Bint &a,const Bint &b)
{
	for(int i=a.size()-1; ~i; --i)
		if(a[i]!=b[i])return 1;
	return 0;
}

加法

Bint operator+(const Bint &a,const Bint &b)
{
	return b.any()?(a^b)+((a&b)<<1):a;
}
Bint& operator+=(Bint &a,const Bint &b)
{
	return a=a+b;
}

减法

Bint operator-(const Bint &a)
{
	return Bint(1)+~a;
}
Bint& operator-=(Bint &a,const Bint &b)
{
	return a+=-b;
}
Bint operator-(Bint a,const Bint &b)
{
	return a-=b;
}

乘法

Bint operator*(Bint a,Bint b)
{
	Bint r(0);
	for(; b.any(); b>>=1,a+=a)if(b[0])r+=a;
	return r;
}
Bint& operator*=(Bint &a,const Bint &b)
{
	return a=a*b;
}

整除、取模

Bint operator%=(Bint &dividend,const Bint &divisor)
{
	if(dividend<divisor||divisor.none())return dividend;
	Bint c,res=0;
	for(int k; divisor<dividend;)
	{
		for(k=0,c=divisor; !(dividend<c); c<<=1,++k)
			if(dividend<divisor+c)
			{
				res+=1<<k;
				break;
			}
		if(dividend<divisor+c)break;
		res+=1<<(k-1);
		dividend-=c>>1;
	}
	return dividend;//res是商
}

输入输出

bitset已经原生重载了输入输出运算符,避免歧义。

istream& getb(istream &is,Bint &val)
{
	int sign=1,ch=is.get();
	for(; !isdigit(ch)&&ch!=EOF; ch=is.get())
		if(ch=='-')
			sign=-sign;
	for(val=0; isdigit(ch); ch=is.get())
		val=(val<<3)+(val<<1)+(ch^'0');
	if(sign==-1)val=-val;
	return is.putback(ch);
}
ostream& putb(ostream &os,const Bint &val)
{
	if(Bint(9)<val)putb(os,val/10);
	return os.put(val.to_ulong()%10+'0');
}

高精度无符号整数

构造

预留位宽调整,使用时根据高精度数的位数和高精度乘法的限制调节位宽。

struct Wint:vector<int>//继承vector
{
	static const int width=9,base=1e9;
	Wint(unsigned long long n=0)//普通初始化,当整型数和Wint同时运算时会提升至Wint
	{
		for(; n; n/=base)push_back(n%base);
	}
	explicit Wint(const string &s)//字符串初始化函数,未判断字符串合法情况
	{
		for(int len=int(s.size()-1)/width+1,b,e,i=0; i!=len; ++i)
			for(e=s.size()-i*width,b=max(0,e-width),push_back(0); b!=e; ++b)
				back()=back()*10+s[b]-'0';
		trim(0);
	}
	Wint& trim(bool up=1)//去前导0,是否需要进位,很常用的小函数,为方便返回自身
	{
		for(int i=1; up&&i<size(); ++i)
		{
			if(at(i-1)<0)--at(i),at(i-1)+=base;
			if(at(i-1)>=base)at(i)+=at(i-1)/base,at(i-1)%=base;
		}
		while(!empty()&&back()<=0)pop_back();
		for(; up&&!empty()&&back()>=base; at(size()-2)%=base)
			push_back(back()/base);
		return *this;
	}
};

输入输出

istream& operator>>(istream &is,Wint &n)
{
	string s;//懒
	return is>>s,n=Wint(s),is;
}
ostream& operator<<(ostream &os,const Wint &n)
{
	if(n.empty())return os.put('0');
	os<<n.back();
	char ch=os.fill('0');
	for(int i=n.size()-2; ~i; --i)
		os.width(n.width),os<<n[i];
	return os.fill(ch),os;
}

大小比较

vector自带大小比较为字典序比较,!===运算可省,其余运算需要时一定记得重载!

bool operator<(const Wint &a,const Wint &b)
{
	if(a.size()!=b.size())return a.size()<b.size();
	for(int i=a.size()-1; ~i; --i)
		if(a[i]!=b[i])return a[i]<b[i];
	return 0;
}
bool operator>(const Wint &a,const Wint &b)
{
	return b<a;
}
bool operator<=(const Wint &a,const Wint &b)
{
	return !(a>b);
}
bool operator>=(const Wint &a,const Wint &b)
{
	return !(a<b);
}

加法和减法

减法,当被减数小于减数时减为0。

Wint& operator+=(Wint &a,const Wint &b)
{
	a.resize(max(a.size(),b.size()));//保证有足够的位数
	for(int i=0; i!=b.size(); ++i)a[i]+=b[i];
	return a.trim();//单独进位防自运算
}
Wint operator+(Wint a,const Wint &b)
{
	return a+=b;
}
Wint& operator++(Wint &a)//前置版本
{
	return a+=1;//懒
}
Wint operator++(Wint &a,int)//后置版本
{
	Wint b=a;
	return ++a,b;
}
Wint& operator-=(Wint &a,const Wint &b)//a<b会使a变为0
{
	a.resize(max(a.size(),b.size()));//保证有足够的位数
	for(int i=0; i!=b.size(); ++i)a[i]-=b[i];
	return a.trim();//单独进位防自运算
}
Wint operator-(Wint a,const Wint &b)
{
	return a-=b;
}
Wint& operator--(Wint &a)//前置版本
{
	return a-=1;//懒
}
Wint operator--(Wint &a,int)//后置版本
{
	Wint b=a;
	return --a,b;
}

乘法

同时给出高精度乘法的FFT优化FNTT优化

Wint& operator*=(Wint &a,const Wint &b)
{
	Wint c;
	c.assign(a.size()+b.size()+2,0);//多开位用于进位
	for(int j=0,k,l; j<b.size(); ++j)
		if(b[j])//稀疏优化,特殊情况很有效
			for(int i=0; i<a.size(); ++i)
			{
				unsigned long long n=a[i];
				for(n*=b[j],k=i+j; n; n/=c.base)
					c[k++]+=n%c.base;
				for(l=i+j; c[l]>=c.base||l<k; c[l++]%=c.base)
					c[l+1]+=c[l]/c.base;
			}
	return swap(a,c),a.trim(0);
}
Wint operator*(Wint a,const Wint &b)
{
	return a*=b;
}

一种效率略高但对位宽有限制的写法

乘法算完后统一进位效率高,防止乘法溢出(unsigned long long范围0~1.8e19),位宽为9时size()不能超过18(十进制162位),位宽为8时size()不能超过1800(十进制14400位)等等。

Wint& operator*=(Wint &a,const Wint &b)
{
    vector<unsigned long long> n(a.size()+b.size()+2,0);//防爆int,多开位用于进位
    for(int j=0; j!=b.size(); ++j)
        if(b[j])//稀疏优化,特殊情况很有效
            for(int i=0; i!=a.size(); ++i)
                n[i+j]+=(unsigned long long)a[i]*b[j];
    for(int i=1; i<n.size(); ++i)//这里用<防止位数0,单独进位防自运算
        n[i]+=n[i-1]/a.base,n[i-1]%=a.base;
    while(!n.empty()&&!n.back())n.pop_back();
    return a.assign(n.begin(),n.end()),a;
}

除法和取模

需要重载<+=-=*=

Wint& operator/=(Wint &a,Wint b)
{
	Wint r,c,d=b.base/(b.back()+1);
	a*=d,b*=d,c.assign(a.size(),0);
	for(int i=a.size()-1; ~i; --i)
	{
		r.insert(r.begin(),a[i]);
		unsigned long long s=0;
		for(int j=b.size(); j+1>=b.size(); --j)//b.size()==0肯定第一行就出问题的
			s=s*b.base+(j<r.size()?r[j]:0);
		for(d=c[i]=s/b.back(),d*=b; r<d; r+=b)--c[i];
		r-=d;
	}
	return swap(a,c),a.trim(0);//r为加倍后的余数,可通过高精度除低精度得到真正余数,此处略
}
Wint operator/(Wint a,const Wint &b)
{
	return a/=b;
}
Wint& operator%=(Wint &a,const Wint &b)
{
	return a-=a/b*b;
}
Wint operator%(Wint a,const Wint &b)
{
	return a%=b;
}

开平方

快速解法

51Nod上抄来的代码,Wint::width设置为9的时候能140ms极速A题,设置为1的时候输入101输出11?待研究。

bool cmp(const Wint &a,ll c,int d,const Wint &b)
{
	ll l=-(a.base<<1),t=0;
	if(b.size()<a.size()+d&&c)return 1;
	for(int i=b.size()-1; l<=t&&t<=0&&i>d; --i)
		t=t*a.base+c*(i-d-1<a.size()?a[i-d-1]:0)-b[i];
	for(int i=d-1; l<=t&&t<=0&&~i; --i)
		t=t*a.base-b[i];
	return t>0;
}
void sub(Wint &a,const Wint &b,ll k,int d)
{
	int l=b.size()+d;
	for(int i=d+1; i<=l; ++i)
	{
		ll tmp=a[i]-k*b[i-d-1];
		if(tmp<0)
		{
			a[i+1]+=(tmp-a.base+1)/a.base;
			a[i]=tmp-(tmp-a.base+1)/a.base*a.base;
		}
		else a[i]=tmp;
	}
	for(int i=l+1; i<a.size()&&a[i]<0; ++i)
	{
		a[i+1]+=(a[i]-a.base+1)/a.base;
		a[i]-=(a[i]-a.base+1)/a.base*a.base;
	}
	a.trim(0);
}
Wint sqrt(Wint a)
{
	Wint ret;
	ret.assign((a.size()+1)>>1,0);
	for(int i=ret.size()-1; ~i; --i)
	{
		int l=0,r=a.base,m=ret[i]=(l+r)>>1;
		while(r-l>1)
		{
			if(cmp(ret,m,i-1,a))r=m;
			else l=m;
			m=ret[i]=(l+r)>>1;
		}
		sub(a,ret,m,i-1);
		ret[i]+=m;
	}
	ret.trim(0);
	for(int i=0; i<ret.size(); ++i)ret[i]>>=1;
	return ret;
}

常规牛顿迭代

只能过51Nod上的5个点。可是好敲啊。

Wint sqrt(const Wint &a)
{
	Wint b=a,c=(b+1)/2;
	while(b!=c)swap(b,c),c=(b+a/b)/2;
	return c;
}

常规按位二分

过五个点。

Wint sqrt(const Wint &a)
{
	Wint ret,t;
	ret.assign((a.size()+1)>>1,0);
	for(int i=ret.size()-1,l,r; ~i; --i)
	{
		for(l=0,r=a.base; r-l>1;)
		{
			ret[i]=l+(r-l)/2;
			t=ret*ret;
			if(a<t)r=ret[i];
			else l=ret[i];
		}
		if(!l&&i==ret.size()-1)ret.pop_back();
		else ret[i]=l;
	}
	return ret;
}

C++语言相关

开栈

c++

#pragma comment(linker,"/STACK:102400000,102400000")

g++

一定要最后写一句exit(0);退出程序,否则会得到非零退出的错误,可能RE。

int size=256<<20;//256MB
char *p=(char*)malloc(size)+size;
__asm__ __volatile__("movq %0, %%rsp\n"::"r"(p));//64bit

读入优化

仿C++IO流沙雕版

使用前对比使用后

struct Istream
{
	char b[20<<20],*i,*e;//20MB
	Istream(FILE* in):i(b),e(b+fread(b,sizeof(*b),sizeof(b)-1,in)) {}
	bool eof()const
	{
		return i==e;
	}
	Istream& operator>>(long long &val)
	{
		return val=strtoll(i,&i,10/*进制,取值2~36*/),*this;
	}
	Istream& operator>>(ll &val)//极限快
	{
		while(*i<'0')++i;//无符号
		for(val=0; *i>='0'; ++i)val=(val<<3)+(val<<1)+*i-'0';
		return *this;
	}
	Istream& operator>>(double &val)
	{
		return val=strtod(i,&i),*this;
	}
	Istream& operator>>(string &s)
	{
		while(!eof()&&isspace(*i))++i;
		for(s.clear(); !eof()&&!isspace(*i); ++i)s+=*i;
		return *this;
	}
} kin(stdin);
#define cin kin

C文件指针版

ll getll(FILE* in=stdin)
{
	ll val=0,sgn=1,ch=getc(in);
	for (; !isdigit(ch)&&ch!=EOF; ch=getc(in))
		if(ch=='-')sgn=-sgn;
	for (; isdigit(ch); ch=getc(in))
		val=val*10+ch-'0';
	return ungetc(ch,in),sgn*val;
}
double getd(FILE* in=stdin)
{
	double val=getll(in),p=val<0?-1:1;
	ll ch=getc(in);
	if(ch=='.')
		for(ch=getc(in); isdigit(ch); ch=getc(in))
			val+=(p/=10)*(ch-'0');
	return ungetc(ch,in),val;
}

运算符优先级

算法库

a为容器时直接传迭代器;a为数组,那么beg变成a,end变成a+m,其中m为a的大小(即传地址)。

//用排序sort、去重unique、二分查找lower_bound实现离散化
sort(beg,end);
int n=unique(beg,end)-beg;//离散化后元素个数,vector可直接resize
int k=lower_bound(beg,beg+n,w)-beg;//k为经排序后原来的元素w离散化后对应的值(从0开始)

//以下算法,用于求给定区间的后一\前一字典序排列,不存在返回0,存在返回1
next_permutation(beg,end);
prev_permutation(beg,end);

nth_element(beg,beg+n,end);//将第n大的元素放在第n位,并且比其小的元素都在它前面,比它大的元素都在后面,线性,但不保证有序
max_element(beg,end);//最大值地址
min_element(beg,end);//最小值地址

copy(beg,end,beg2);//指定区间拷贝到beg2开始的地址,需要保证空间足够
fill(beg,end,val);//区间赋值,比memset安全
reverse(beg,end);//区间翻转

容器库/数据结构

struct tm,time_t,mktime

pair

迭代器

顺序容器

c.insert(p,n,t);//在迭代器p指向的位置插入n个值t,原有元素后移;vector、deque很慢
c.insert(p,b,e);//在迭代器p指向的位置插入元素区间[b,e),原有元素后移;[b,e)不能指向c;vector、deque很慢

p=c.erase(p);//删除迭代器p指向的元素,并返回被删元素后面一个元素的迭代器;若p为end,则返回值也为end;vector、deque很慢
p=c.erase(b,e);//删除[b,e)内元素,其余同上

string

string也属于顺序容器,用法(以及后台的实现)与vector<char>相当。string还附带其他操作:

string s1("c_stri");//用c_string初始化
string s2(s1,pos,n);//用s1从pos位开始的n个初始化s2,n省略时到末尾

s1.substr(pos,n);//s1从pos位开始的n个字符构成的子串,n省略时到末尾

//以下均为查找函数均为O(N*N)的暴力查找而非匹配算法,其中args均为s2,pos,代表从pos开始查找s2,s2亦可为单个字符,pos默认0;不存在返回一个值s.npos
s.find(args);//第一个args
s.rfind(args);//最后一个args,即反向查找
s.find_first_of(args);//s中args任意一个字符第一个出现的位置
s.find_last_of(args);//最后一个
s.find_first_not_of(args);//s中非args中任意一个字符第一个出现的位置
s.find_last_not_of(args);//最后一个

s1+s2;//返回s1尾部缀上s2的结果
s1+=s2;//s1=s1+s2

< > <= >= == !=//比较字典序

关联容器

//初始化,其中T、T1都要是支持<运算符的类型
set<T> s,s1(beg,end);
multiset<T> ms,ms1(beg,end);
map<T1,T2> m;
multimap<T1,T2> mm;

//自定义排序的初始化,以set为例,传一个比较类进去
strcut Cmp
{
	bool operater()(const T &t1,const T &t2)
	{
		return t1>t2;
	}
};//这个Cmp其实就是greater<T>
set<T,Cmp> s2;

_s.insert(t);//s或ms中插一个t
_m.insert(make_pair(t1,t2));//m和mm插入需要插入一个pair

p=c.begin();//获得c的首迭代器,指向c最小的元素,效率O(logN);insert操作后失效,需要重新获得
p=c.end();//c的尾迭代器,指向c最大的元素后一位,效率O(logN);insert操作后失效,需要重新获得
//对于map和multimap的迭代器,解引用之后是一个pair

++p;--p;//移动迭代器,效率O(logN),移动到边界后不再移动

c.erase(t);//删除值为t的所有元素,map类的t为pair,返回实际删除元素的数量
q=c.erase(p);//删除迭代器p指向的元素,返回后一个位置的迭代器
q=c.erase(beg,end);//返回[beg,end)返回end

m[t1];//m中下标为t1的索引,若不存在则会执行T2的默认初始化,可能会插入新元素所以m不能为const;否则应使用find代替;mm不支持

c.count(t);//返回c中元素t的数量,不存在为0,非multi容器最多为1
c.find(t);//返回c中第一个值为t的迭代器,不存在返回c.end();
c.lower_bound(k);//指向第一个不小于k元素的迭代器,不存在返回c.end()
c.upper_bound(k);//指向第一个大于k元素的迭代器,不存在返回c.end()
c.equal_range(k);//返回一个迭代器pair表示值为k的元素的范围,不存在均为c.end()
//map和multiset中参数t均表示第一关键字

猜你喜欢

转载自blog.csdn.net/w_weilan/article/details/83451549
今日推荐