ACM竞赛模版

文章目录

常用板子

基本模板

#include<bits/stdc++.h>
using namespace std;
typedef unsigned long long ull;
typedef long long ll;
template<class T1,class T2>T1 ksm(T1 a,T2 b){T1 ans=1;while(b){if(b&1)ans=ans*a;a=a*a;b>>=1;}return ans;}
template<class T1,class T2,class T3>T1 ksm(T1 a,T2 b,T3 mod){T1 ans=1;a%=b;while(b){if(b&1)ans=(ans*a)%mod;a=(a*a)%mod;b>>=1;}return ans;}
template<class T>T gcd(T a,T b){T r;while(b>0){r=a%b;a=b;b=r;}return a;}
template<class T>T sqr(T a){return a*a;}
#define inf (INT_MAX/2)
#define lowbit(i) ((i)&-(i))
#define Max(x,y) x=max(x,y)
#define Min(x,y) x=min(x,y)
#define Out(t) puts((t)?"YES":"NO")
mt19937 rnd(time(0));
mt19937_64 rnd64(time(0));
const double pi=acos(-1.);
const double eps=1e-10;
const int def=100010;
const int mod=1000000007;

int main()
{	int _=1,__=1;
	for(((1)?scanf("%d",&_):EOF);_;_--,__++){
		
	}
	return 0;
}

小数比较

const double eps=1e-4;
inline int fcmp(double a,double b)//a>b,1;a==b,0;a<b,-1;
{
	if(a-b>eps)return 1;
	if(b-a>eps)return -1;
	return 0;
}

快读板子

struct IO {
	inline char read(){
		static const int IN_LEN=1<<18|1;
		static char buf[IN_LEN],*s,*t;
		return (s==t)&&(t=(s=buf)+fread(buf,1,IN_LEN,stdin)),s==t?-1:*s++;
	}

	inline IO & operator >> (char &c){
		for(c=read();c=='\n'||c=='\r'||c==' ';c=read());
		return *this;
	}

	inline IO & operator >> (string &s){
		static char c;
		for(c=read();c=='\n'||c=='\r'||c==' ';c=read());
		for(s="";c!='\n'&&c!=' '&&c!=-1;c=read())s+=c;
		return *this;
	}

	template <typename _Tp> inline IO & operator >> (_Tp&x){
		static char c11,boo;
		for(c11=read(),boo=0;!isdigit(c11);c11=read()){
			if(c11==-1)return *this;
			boo|=c11=='-';
		}
		for(x=0;isdigit(c11);c11=read())x=x*10+(c11^'0');
		boo&&(x=-x);
		return *this;
	}

	inline string getline()
	{
		static char c;
		string s="";
		for(c=read();c=='\n'||c=='\r';c=read());
		for(;c!='\n'&&c!='\r'&&c!=-1;c=read())s+=c;
		return s;
	}
}io;

高精度板子(带符号,自适应长度)

struct bign{
	int bit=6,base=pow(10,bit)+1e-10;
	int fu=1;
	vector<ll>d;

	bign(int num=0){*this=num;}
	bign(ll num){*this=num;}
	bign(const string &num){*this=num;}
	int len()const{return d.size();}
	void resize(int x){d.resize(x,0);};
	bool iszero(){return len()==1&&d[0]==0;}
	void clean(){while(d.size()>1&&!d.back())d.pop_back();if(iszero())fu=0;}

	bign operator =(const string &num)
	{
		int n=num.length(),q=(num[0]=='-')?1:0;
		fu=q?-1:1;
		d.resize((n-q+bit-1)/bit);
		for(int i=n-1,k=0;i>=0;i-=bit,++k){
			ll now=0;
			for(int j=bit-1;~j;j--)if(i-j>=q)
				now=now*10+num[i-j]-'0';
			d[k]=now;
		}
		return *this;
	}
	bign operator =(ll num){return *this=to_string(num);}
	bign operator -()const{bign c=*this;c.fu=-c.fu;return c;}

	bign operator +(const bign &b)const
	{
		if(fu<0&&b.fu>0)return -(-*this-b);
		if(fu>0&&b.fu<0)return *this-(-b);
		if(fu<0&&b.fu<0)return -((-*this)+(-b));
		bign c;
		int n=len(),m=b.len(),q=max(n,m)+1;
		c.resize(q);
		for(int i=0;i<q-1;i++){
			if(i<n)c.d[i]+=d[i];
			if(i<m)c.d[i]+=b.d[i];
			if(c.d[i]>=base){c.d[i+1]++;c.d[i]-=base;}
		}
		c.clean();
		return c;
	}
	bign operator -(const bign &b)const
	{
		if(fu<0&&b.fu>0)return -((-*this)+b);
		if(fu>0&&b.fu<0)return *this+(-b);
		if(fu<0&&b.fu<0)return -((-*this)-(-b));
		int m=b.len();
		bign c=*this;
		if(*this<b)return -(b-*this);
		for(int i=0;i<m;i++){
			c.d[i]-=b.d[i];
			if(c.d[i]<0){--c.d[i+1];c.d[i]+=base;}
		}
		c.clean();
		return c;
	}
	bign operator *(const bign &b)const
	{	bign c;
		c.resize(len()+b.len()+5);
		for(int i=0;i<len();i++)
			for(int j=0;j<b.len();j++){
				c.d[i+j]+=d[i]*b.d[j];
				if(c.d[i+j]>=2e9){
					c.d[i+1]+=c.d[i]/base;
					c.d[i]%=base;
				}
			}
		for(int i=0;i<c.len()-1;i++)if(c.d[i]>=base){
			c.d[i+1]+=c.d[i]/base;
			c.d[i]%=base;
		}
		c.clean();
		c.fu=fu*b.fu;
		return c;
	}
	bign operator /(const bign &b)const
	{	bign c=*this,a=0;
		for(int i=len()-1;i>=0;i--){
			a=a*base+d[i];
			int j;
			for(j=0;j<base;j++)
				if(a<b*(j+1))
					break;
			c.d[i]=j;
			a=a-b*j;
		}
		c.fu=fu*b.fu;
		c.clean();
		return c;
	}
	bign operator %(const bign &b)const
	{	bign a=0;
		for(int i=len()-1;i>=0;i--){
			a=a*base+d[i];
			int j;
			for(j=0;j<base;j++)
				if(a<b*(j+1))
					break;
			a=a-b*j;
		}
		a.fu=fu*b.fu;
		a.clean();
		return a;
	}
	bool operator <(const bign &b)const{
		if(fu>0&&b.fu<0)return false;
		if(fu<0&&b.fu>0)return true;
		if(fu<0&&b.fu<0)return -*this>-b;
		if(len()!=b.len())return len()<b.len();
		for(int i=len()-1;i>=0;i--)
			if(d[i]!=b.d[i])
				return d[i]<b.d[i];
		return false;
	}
	bool operator >(const bign& b) const{return b<*this;}
	bool operator<=(const bign& b) const{return !(b<*this);}
	bool operator>=(const bign& b) const{return !(*this<b);}
	bool operator!=(const bign& b) const{return b<*this||*this<b;}
	bool operator==(const bign& b) const{return !(b<*this)&&!(b>*this);}


	string str()const
	{	string s="";
		stringstream ss;
		if(fu==-1)ss<<'-';
		ss<<d.back();
		for(int i=len()-2;i>=0;i--)ss<<setw(bit)<<setfill('0')<<d[i];
		ss>>s;
		return s;
	}
};

istream& operator >>(istream& in, bign &x)
{	string s;
	in>>s;
	x=s;
	return in;
}
 
ostream& operator <<(ostream& out,const bign &x)
{
	out<<x.str();
	return out;
}

分数板子

struct frac{
	ll fz,fm;
	frac(){fz=0;fm=1;}
	frac(ll a){*this=a;}
	frac(ll a,ll b){assert(b);fz=a;fm=b;yue();}
	void yue(){
		if(fm<0){fz=-fz;fm=-fm;}
		ll g=gcd(abs(fz),abs(fm));
		fz/=g;fm/=g;
	}
	frac operator=(const ll &a){
		fz=a;fm=1;
		return *this;
	}
	friend bool operator <(const frac &a,const frac &b){return a.fz*b.fm<a.fm*b.fz;}
	friend bool operator >(const frac &a,const frac &b){return b<a;}
	friend bool operator<=(const frac &a,const frac &b){return !(b<a);}
	friend bool operator>=(const frac &a,const frac &b){return !(a<b);}
	friend bool operator==(const frac &a,const frac &b){return !(a<b)&&!(b<a);}
	friend bool operator!=(const frac &a,const frac &b){return a<b||b<a;}
	friend frac operator+(frac &a,frac &b){
		frac c(a.fz*b.fm+b.fz*a.fm,a.fm*b.fm);
		c.yue();
		return c;
	}
	friend frac operator-(frac &a,frac &b){
		frac c(-b.fz,b.fm);
		return a+c;
	}
	friend frac operator*(frac &a,frac &b){
		frac c(a.fz*b.fz,a.fm*b.fm);
		c.yue();
		return c;
	}
	friend frac operator/(frac &a,frac &b){
		frac c(b.fm,b.fz);
		return a*c;
	}
	friend frac operator+=(frac &a,frac &b){return a=a+b;}
	friend frac operator-=(frac &a,frac &b){return a=a-b;}
	friend frac operator*=(frac &a,frac &b){return a=a*b;}
	friend frac operator/=(frac &a,frac &b){return a=a/b;}
};

组合数板子

ll jc[def],ny[def];

void init_jcny(int n,int mod)
{
	jc[0]=ny[0]=1;
	for(int i=1;i<=n;i++)jc[i]=jc[i-1]*i%mod;
	ny[n]=ksm(jc[n],mod-2,mod);
	for(int i=n-1;i>=1;i--)ny[i]=ny[i+1]*(i+1)%mod;
}

//n>=m
inline ll A(int n,int m){return jc[n]*ny[n-m]%mod;}
inline ll C(int n,int m){return  A(n,m)*ny[m]%mod;}

线筛板子

bool pr[def];
int p[def];

void shai(int n)
{
	p[0]=0;
	for(int i=2;i<=n;i++){
		if(!pr[i])
			p[++p[0]]=i;
		for(int j=1;j<=p[0]&&i*p[j]<=n;j++){
			pr[i*p[j]]=true;
			if(i%p[j]==0)break;
		}
	}
}

字符串

字符串hash

const int base=23333;
ull po[def];
void init(int n)
{
    po[0]=1;
    for(int i=1;i<=n;i++)
        po[i]=po[i-1]*base;
}

ull shash(const string &s)//单hash
{	int n=s.length();
 	h[0]=s[0];
	for(int i=1;i<n;i++)
		h[i]=h[i-1]*base+s[i];
	return ans;
}

ull get(int l,int r)
{
    return h[r]-h[l-1]*po[r-l+1];
}

KMP O ( n ) O(n) O(n)

int nxt[def];

template<class T>
void kmp_init(const vector<T> &s)
{	int n=s.size();
	nxt[0]=-1;
	for(int i=1,j=-1;i<n;i++){
		while(~j&&s[i]!=s[j+1])j=nxt[j];
		if(s[i]==s[j+1])j++;
		nxt[i]=j;
	}
}

template<class T>
void kmp(const vector<T> &s1,const vector<T> &s2)
{	int n=s1.size(),m=s2.size();
	//kmp_init(s2);
	for(int i=0,j=-1;i<n;i++){
		while(~j&&s1[i]!=s2[j+1])j=nxt[j];
		if(s1[i]==s2[j+1])j++;
		if(j==m-1){
			printf("%d\n",i-j+1);
			j=nxt[j];
		}
	}
}

Z函数(扩展KMP)

int z[def],p[def];

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

void exkmp(const string &s,const string &t)
{	int n=s.length(),m=t.length();
	exkmp_init(t);
	for(int i=0;i<n;i++)p[i]=0;
	for(int i=0,l=-1,r=-1;i<n;i++){
		if(i<=r)p[i]=min(z[i-l],r-i+1);
		while(i+p[i]<n&&t[p[i]]==s[i+p[i]])p[i]++;
		if(i+p[i]-1>r)l=i,r=i+p[i]-1;
	}
}

后缀数组SA

int rnk[def*def],num[def*def],sa[def*def],tp[def*def],h[def*def];

void get_sa(int *a,int n,int m)//长度为n,字符集范围0~m
{
	for(int i=1;i<=n;i++)rnk[i]=a[i],tp[i]=i;
	for(int i=1;i<=m;i++)num[i]=0;
	for(int i=1;i<=n;i++)num[rnk[i]]++;
	for(int i=1;i<=m;i++)num[i]+=num[i-1];
	for(int i=n;i>=1;i--)sa[num[rnk[tp[i]]]--]=tp[i];
	//从大到小枚举第二关键字tp[i],用rnk[tp[i]]得到对应的第一关键字的位置,用num[rnk[tp[i]]]得到对应的排名,--用来错位,得到的排名就是sa
	for(int l=1,p=0;p<n;m=p,l<<=1){
		p=0;
		for(int i=1;i<=l;i++)tp[++p]=n-l+i;
		for(int i=1;i<=n;i++)if(sa[i]>l)tp[++p]=sa[i]-l;
		for(int i=1;i<=m;i++)num[i]=0;
		for(int i=1;i<=n;i++)num[rnk[i]]++;
		for(int i=1;i<=m;i++)num[i]+=num[i-1];
		for(int i=n;i>=1;i--)sa[num[rnk[tp[i]]]--]=tp[i];
		for(int i=1;i<=n;i++)tp[i]=rnk[i];
		rnk[sa[1]]=p=1;
		for(int i=2;i<=n;i++)
			rnk[sa[i]]=(tp[sa[i-1]]==tp[sa[i]]&&tp[sa[i-1]+l]==tp[sa[i]+l])?p:++p;
		//上一轮排名相同,这一轮排名也相同
	}
}
void get_height(int *a,int n)
{
	int k=0;
	for (int i=1;i<=n;i++){
		int j=sa[rnk[i]-1];
		if(k)k--;
		while(a[j+k]==a[i+k])k++;
		h[rnk[i]]=k;
	}
}
//经典应用
//两个后缀的最大公共前缀:
//lcp(x,y)=min(h[i]) i={rnk[x]+1,...,rnk[y]}
//lcp(x,x)=n-x+1;
//可重叠最长重复子串:max(h[i])
//不可重叠的最长重复子串:二分答案x,对h分组,保证每组的min(h[i])>=x
//本质不同的子串数量:sum(len-sa[i]+1-h[i])

后缀自动机SAM

struct Suffix_Automaton{

	static const int maxsize=26;
	struct state{
		int len,link;
		int nxt[maxsize];
	}st[def*2];
	int sz,last;

	int create()
	{	int cur=sz++;
		for(int i=0;i<maxsize;i++)st[cur].nxt[i]=-1;
		st[cur].len=0;
		st[cur].link=-1;
		return cur;
	}

	void init()
	{
		sz=0;
		last=create();
	}

	void extend(int c)
	{
		int cur=create();
		st[cur].len=st[last].len+1;
		int p=last;
		while(~p&&!~st[p].nxt[c]){
			st[p].nxt[c]=cur;
			p=st[p].link;
		}
		if(!~p){
			st[cur].link=0;
		}else{
			int q=st[p].nxt[c];
			if(st[p].len+1==st[q].len){
				st[cur].link=q;
			}else{
				int clone=create();
				for(int i=0;i<maxsize;i++)st[clone].nxt[i]=st[q].nxt[i];
				st[clone].len=st[p].len+1;
				st[clone].link=st[q].link;
				while(~p&&st[p].nxt[c]==q){
					st[p].nxt[c]=clone;
					p=st[p].link;
				}
				st[q].link=st[cur].link=clone;
			}
		}
		last=cur;
		// sum表示本质不同的子串个数
		// int len=st[cur].len-st[st[cur].link].len;
		// sum+=len;
		// return len;
	}

};

AC自动机

struct Aho_Corasick_Automaton{
	static const int m=26;
	#define nt s[i]-'a'
	int cnt,ch[def][m],fail[def],val[def];

	Aho_Corasick_Automaton(){clear();}

	void clear(){cnt=0;}

	void ins(const char *s){
		int n=strlen(s),now=0;
		for(int i=0;i<n;i++){
			if(!ch[now][nt]){
				ch[now][nt]=++cnt;
				memset(ch[cnt],0,sizeof(ch[cnt]));
				val[cnt]=0;
			}
			now=ch[now][nt];
		}
		val[now]++;
	}

	void build(){
		queue<int>que;
		for(int i=0;i<m;i++)if(ch[0][i])fail[ch[0][i]]=0,que.push(ch[0][i]);
		while(!que.empty()){
			auto now=que.front();
			que.pop();
			for(int i=0;i<m;i++)
				if(ch[now][i])fail[ch[now][i]]=ch[fail[now]][i],que.push(ch[now][i]);
				else ch[now][i]=ch[fail[now]][i];
		}
	}

	int query(const char *s){
		int n=strlen(s),now=0,ans=0;
		for(int i=0;i<n;i++){
			now=ch[now][nt];
			for(int t=now;t&&~val[t];t=fail[t]){
				ans+=val[t];
				val[t]=-1;
			}
		}
		return ans;
	}
	#undef nt
}ac;

数学

扩展gcd

e x g c d ( a , b , x , y ) exgcd(a,b,x,y) exgcd(a,b,x,y)求解 a x + b y = gcd ⁡ ( a , b ) ax+by=\gcd(a,b) ax+by=gcd(a,b)解得 x 1 , y 1 x1,y1 x1,y1

a ∗ x 1 ∗ c / gcd ⁡ ( a , b ) + b ∗ y 1 ∗ c / gcd ⁡ ( a , b ) = c a*x1*c / \gcd(a,b)+b*y1*c/\gcd(a,b)=c ax1c/gcd(a,b)+by1c/gcd(a,b)=c

解集为 { ( x , y ) ∣ x = x 1 + k ∗ b / gcd ⁡ ( a , b ) , y = y 1 − k ∗ a / gcd ⁡ ( a , b ) , k ∈ Z } \{(x,y)|x=x1+k*b/\gcd(a,b), y=y1-k*a/\gcd(a,b), k \in \Z\} {(x,y)x=x1+kb/gcd(a,b),y=y1ka/gcd(a,b),kZ}

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

逆元

阶乘逆元 O ( n ) O(n) O(n)

ll jc[def],ny[def];

void init_jcny(int n,int mod)
{
	jc[0]=ny[0]=1;
	for(int i=1;i<=n;i++)jc[i]=jc[i-1]*i%mod;
	ny[n]=ksm(jc[n],mod-2,mod);
	for(int i=n-1;i>=1;i--)ny[i]=ny[i+1]*(i+1)%mod;
}

数逆元 O ( n ) O(n) O(n)

ll inv[def];

void init_numny(int n,int p)
{
	inv[1]=1;
	for(int i=2;i<=n;i++)
		inv[i]=(ll)(p-p/i)*inv[p%i]%p;
}

矩阵

基本模版

struct matrix{
	//基本函数及定义
	static const int N=410;
	ll a[N][N];
	int n,m;

	matrix(){n=0;}
	matrix(int n,int m,int x=0){init(n,m,x);}
	void clear(){for(int i=0;i<=n;i++)for(int j=0;j<=m;j++)a[i][j]=0;}
	void init(int n,int m,int x=0){
		this->n=n;
		this->m=m;
		*this=x;
	}
	matrix operator =(ll x){
		clear();
		for(int i=0;i<n;i++)
			a[i][i]=x;
		return *this;
	}
	friend matrix operator+(const matrix &a,ll b){return a+matrix(a.n,a.m,b);}
	friend matrix operator+(const matrix &a,const matrix &b){
		assert(a.n==b.n&&a.m==b.m);
		matrix c(a.n,a.m);
		for(int i=0;i<a.n;i++)
			for(int j=0;j<a.m;j++)
				c.a[i][j]=(a.a[i][j]+b.a[i][j])%mod;
		return c;
	}
	friend matrix operator-(const matrix &a,ll b){return a-matrix(a.n,a.m,b);}
	friend matrix operator-(const matrix &a,const matrix &b){
		assert(a.n==b.n&&a.m==b.m);
		matrix c(a.n,a.m);
		for(int i=0;i<a.n;i++)
			for(int j=0;j<a.m;j++)
				c.a[i][j]=(a.a[i][j]-b.a[i][j])%mod;
		return c;
	}
	friend matrix operator*(const matrix &a,ll b){return a*matrix(a.n,a.m,b);}
	friend matrix operator*(const matrix &a,const matrix &b){
		assert(a.m==b.n);
		matrix c(a.n,b.m);
		for(int i=0;i<a.n;i++)
			for(int j=0;j<b.m;j++)
				for(int k=0;k<a.m;k++)
					c.a[i][j]=(c.a[i][j]+a.a[i][k]*b.a[k][j]%mod)%mod;
		return c;
	}
	friend bool operator==(const matrix &a,ll b){return a==matrix(a.n,a.m,b);}
	friend bool operator==(const matrix &a,const matrix &b){
		assert(a.n==b.n&&a.m==b.m);
		for(int i=0;i<a.n;i++)
			for(int j=0;j<a.n;j++)
				if(a.a[i][j]!=b.a[i][j])
					return false;
		return true;
	}
	void output()
	{
		for(int i=0;i<n;i++)
			for(int j=0;j<m;j++)
				printf("%lld%c",a[i][j],j==n-1?'\n':' ');
	}

	//额外算法函数定义
	matrix pow(ll b);//矩阵快速幂
	void Gauss();//高斯消元
	bool inv();//矩阵求逆

};

矩阵快速幂

matrix matrix::pow(ll b){
	assert(n==m);
	matrix ans,a=*this;
	ans.init(a.n,a.m,1);
	while(b){
		if(b&1)ans=ans*a;
		a=a*a;
		b>>=1;
	}
	return ans;
}

高斯消元

void matrix::Gauss()//矩阵消元
{
	assert(n==m);
	for(int k=0;k<n;k++){
		int pos=k;
		for(int i=k;i<n;i++)
			if(a[i][k]){
				pos=i;
				break;
			}
		for(int j=0;j<n;j++)swap(a[k][j],a[pos][j]);
		ll ny=ksm(a[k][k],mod-2,mod);
		for(int i=k+1;i<n;i++){
			ll mul=a[i][k]*ny%mod;
			for(int j=n-1;j>=k;j--){
				a[i][j]=a[i][j]-mul*a[k][j]%mod;
				a[i][j]=(a[i][j]%mod+mod)%mod;
			}
		}
	}
}

矩阵求逆

int posi[matrix::N],posj[matrix::N];
bool matrix::inv()
{
	assert(n==m);
	for(int k=0;k<n;k++){
		posi[k]=posj[k]=k;
		for(int i=k;i<n;i++)
			for(int j=k;j<n;j++)if(a[i][j]){
				posi[k]=i;
				posj[k]=j;
				break;
			}
		for(int j=0;j<n;j++)swap(a[k][j],a[posi[k]][j]);
		for(int i=0;i<n;i++)swap(a[i][k],a[i][posj[k]]);
		if(!a[k][k])return false;
		a[k][k]=ksm(a[k][k],mod-2,mod);
		for(int j=0;j<n;j++)if(j!=k)
			a[k][j]=a[k][j]*a[k][k]%mod;
		for(int i=0;i<n;i++)if(i!=k)
			for(int j=0;j<n;j++)if(j!=k)
				a[i][j]=(a[i][j]-a[i][k]*a[k][j]%mod+mod)%mod;
		for(int i=0;i<n;i++)if(i!=k)
			a[i][k]=(mod-a[i][k]*a[k][k]%mod)%mod;
	}
	for(int k=n-1;k>=0;k--){
		for(int j=0;j<n;j++)swap(a[k][j],a[posj[k]][j]);
		for(int i=0;i<n;i++)swap(a[i][k],a[i][posi[k]]);
	}
	return true;
}

素数判定

ll ksc(ll a,ll b,ll mod){ll ans=0;for(;b;b>>=1,a=(a+a)%mod)if(b&1)ans=(ans+a)%mod;return ans;}
ll ksm(ll a,ll b,ll mod){ll ans=1;for(;b;b>>=1,a=ksc(a,a,mod))if(b&1)ans=ksj(ans,a,mod);return ans;}

bool miller_rabin(ll x)
{	ll s=0,t=x-1;
	if(x==2)return true;
	if(x<2||!(x&1))return false;
	for(;!(t&1);t>>=1)s++;
	for(ll a:{2,3,5,7,13,29,37,89})if(a<x){
		ll b=ksm(a,t,x);
		for(int j=1;j<=s;++j){
			ll k=ksm(b,2,x);
			if(k==1&&b!=1&&b!=x-1)return false;
			b=k;
		}
		if(b!=1)return false;
	}
	return true;
}

质因数分解

ll pollard_rho(ll x)
{
	ll c=rnd()%(x-1)+1;
	ll s=0,t=0;
	for(int goal=1;;goal<<=1,s=t){
		ll val=1;
		for(int step=1;step<=goal;step++){
			t=(ksc(t,t,x)+c)%x;
			val=ksc(val,abs(s-t),x);
			if(step%127==0){
				ll d=gcd(val,x);
				if(d>1)return d;
			}
		}
		ll d=gcd(val,x);
		if(d>1)return d;
	}
}
void rho_all(ll x)
{
	if(x==1)return;
	if(miller_rabin(x)){
		//找到了质因子x
		return;
	}
	ll y=x;
	while(y==x)y=pollard_rho(x);
	rho_all(y);rho_all(x/y);
}

裴蜀定理

a x + b y = c , x ∈ Z ∗ , y ∈ Z ∗ ax+by=c, x\in \Z^*, y\in \Z^* ax+by=c,xZ,yZ成立的充要条件是 gcd ⁡ ( a , b ) ∣ c \gcd(a,b)|c gcd(a,b)c

拓展:对多个元也成立

全排列 ( p r e v & n e x t ) (prev\&next) (prev&next)

template<class T>
bool Prev_permutation(T s,T t)
{	T i=t;--i;
	for(;i!=s;--i){
		T pre=i;--pre;
		if(*i<*pre){
			T j=t;
			while(!(*--j<*pre));
			iter_swap(pre,j);
			reverse(i,t);
			return true;
		}
	}
	reverse(s,t);
	return false;
}

template<class T>
bool Next_permutation(T s,T t)
{	T i=t;--i;
	for(;i!=s;--i){
		T pre=i;--pre;
		if(*pre<*i){
			T j=t;
			while(!(*pre<*--j));
			iter_swap(pre,j);
			reverse(i,t);
			return true;
		}
	}
	reverse(s,t);
	return true;
}

错排公式

D n = n D n − 1 + ( − 1 ) n , D 1 = 0 D_n=nD_{n-1}+(-1)^n,D_1=0 Dn=nDn1+(1)n,D1=0

中国剩余定理

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

ll mul(ll a,ll b,ll mod)
{	ll ans=0;
	while(b){
		if(b&1)ans=(ans+a)%mod;
		a=(a+a)%mod;
		b>>=1;
	}
	return ans;
}

//n≡ai(mod bi)
ll CRT(ll *a,ll *b,int k)
{	ll n=1,ans=0;
	for(int i=1;i<=k;i++)n*=b[i];
	for(int i=1;i<=k;i++){
		ll m=n/b[i],x,y;
		exgcd(m,b[i],x,y);
		x=(x%b[i]+b[i])%b[i];
		ans=(ans+mul(mul(m,x,n),a[i],n))%n;
	}
	return ans;
}

扩展中国剩余定理

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

ll mul(ll a,ll b,ll mod)
{	ll ans=0;
	b=(b%mod+mod)%mod;
	while(b){
		if(b&1)ans=(ans+a)%mod;
		a=(a+a)%mod;
		b>>=1;
	}
	return ans;
}

//n≡ai(mod bi)
ll exCRT(ll *a,ll *b,int k)
{	ll n=b[1],ans=a[1];
	for(int i=2;i<=k;i++){
		ll g=__gcd(n,b[i]),d=a[i]-ans,x,y;
		if(d%g)return -1;
		exgcd(n,b[i],x,y);
		x=(mul(d/g,x,b[i])%b[i]+b[i])%b[i];
		ll m=n;
		n=n/g*b[i];
		ans=((ans+mul(m,x,n))%n+n)%n;
	}
	return ans;
}

Lucas定理

计算 C ( n , m ) % p ,   ( p ≤ 1 e 5 ) C(n,m)\%p,\ (p \leq 1e5) C(n,m)%p, (p1e5)

ll Lucas(ll n,ll m,ll p)
{
    if(!m)return 1;
    return (C(n%p,m%p,p)*Lucas(n/p,m/p,p))%p;
}

欧拉筛

bool pr[def];
int p[def],phi[def];

void shai(int n)
{
	p[0]=0;
	phi[1]=1;
	for(int i=2;i<=n;i++){
		if(!pr[i]){
			p[++p[0]]=i;
			phi[i]=i-1;
		}
		for(int j=1;j<=p[0]&&i*p[j]<=n;j++){
			pr[i*p[j]]=true;
			if(i%p[j]==0){
				phi[i*p[j]]=phi[i]*p[j];
				break;
			}else phi[i*p[j]]=phi[i]*(p[j]-1);
		}
	}
}

快速傅立叶变换(FFT)

#define cp complex<double>
void fft(cp *a,int n,int inv)
{
	static int rev[def];
	int bit=log2(n)+eps;
	for(int i=0;i<n;i++){
		rev[i]=(rev[i>>1]>>1)|((i&1)<<(bit-1));
		if(i<rev[i])swap(a[i],a[rev[i]]);
	}
	for(int mid=1;mid<n;mid<<=1){
		cp tmp(cos(pi/mid),inv*sin(pi/mid));//单位根,pi的系数约掉了
		for(int i=0;i<n;i+=mid*2){//mid*2是准备合并序列的长度
			cp omega(1,0);
			for(int j=0;j<mid;j++,omega*=tmp){//扫左半部分,得右半部分的答案
				cp x=a[i+j],y=omega*a[i+j+mid];
				a[i+j]=x+y,a[i+j+mid]=x-y;
			}
		}
	}
}

自适应辛普森积分

//double res=Simpson(l,r,simpson(l,r),1e-6);
//调用方式 Simpson(l,r,simpson(l,r),eps);
inline double fun(double x){
	return (0.0);//需要积分的函数
}
inline double simpson(double l,double r){
	return (r-l)*(fun(l)+fun(r)+4*fun((l+r)/2))/6;
}
double Simpson(double l,double r,double res,double eps){
	double mid=(l+r)/2;
	double L=simpson(l,mid);
	double R=simpson(mid,r);
	if(fabs(L+R-res)<=15*eps)return L+R+(L+R-res)/15;
	return Simpson(l,mid,L,eps/2)+Simpson(mid,r,R,eps/2);
}

计算几何

常用结论

欧几里得距离: d i s 1 = d x 2 + d y 2 dis1=\sqrt{dx^2+dy^2} dis1=dx2+dy2

曼哈顿距离: d i s 2 = ∣ d x ∣ + ∣ d y ∣ dis2=|dx|+|dy| dis2=dx+dy

切比雪夫距离: d i s 3 = max ⁡ ( ∣ d x ∣ , ∣ d y ∣ ) dis3=\max(|dx|,|dy|) dis3=max(dx,dy)

d i s 2 → d i s 3 : ( x , y ) → ( x + y , x − y ) dis2 \to dis3 : (x,y) \to (x+y,x-y) dis2dis3:(x,y)(x+y,xy)

d i s 3 → d i s 2 : ( x , y ) → ( x + y 2 , x − y 2 ) dis3 \to dis2 : (x,y) \to (\frac{x+y}{2},\frac{x-y}{2}) dis3dis2:(x,y)(2x+y,2xy)

用处:去掉 d i s 3 dis3 dis3的取 max ⁡ \max max

以正点为顶点的线段,覆盖的点的个数为 g c d ( d x , d y ) gcd(dx,dy) gcd(dx,dy),其中, d x , d y dx,dy dx,dy分别为限度哪横向占的点书和纵向占的点数。如果 d x = = 0 dx==0 dx==0 d y = = 0 dy==0 dy==0,则覆盖的点数为 d x dx dx d y dy dy

皮克(Pick)定理

Pick 定理:平面上以整点为顶点的简单多边形的面积 = 边上的点数/2 + 内部的点数 - 1

凸包

struct node{
	ll x,y;
	double ang;
	bool operator<(const node &a)const{
		return (ang!=a.ang)?(ang<a.ang):(y!=a.y)?(y<a.y):(x<a.x);
	}
};

inline ll chaji(node a,node b,node c)
{
	return (b.x-a.x)*(c.y-a.y)-(c.x-a.x)*(b.y-a.y);
}

void get_tubao(long n,node *a,node *sta,long &cnt)
{	long pos=0;
	for(long i=1;i<=n;i++)
		if(!pos||a[i].y<a[pos].y)
			pos=i;
		else if(a[i].y==a[pos].y&&a[i].x<a[pos].x)
			pos=i;
	swap(a[1],a[pos]);//pos=1;
	for(long i=2;i<=n;i++)
		a[i].ang=atan2(a[i].y-a[1].y,a[i].x-a[1].x);
	sort(a+2,a+n+1);
	sta[cnt=1]=a[1];
	for(long i=2;i<=n;i++){
		while(cnt>1&&chaji(sta[cnt-1],sta[cnt],a[i])<=0)
			cnt--;
		sta[++cnt]=a[i];
	}
}

旋转卡壳

struct node{
	ll x,y;
	double ang;
	bool operator<(const node &a)const{
		return (ang!=a.ang)?(ang<a.ang):(y!=a.y)?(y<a.y):(x<a.x);
	}
}a[def];

inline ll chaji(node a,node b,node c)
{
	return (b.x-a.x)*(c.y-a.y)-(c.x-a.x)*(b.y-a.y);
}

inline ll dis(node a,node b)
{
	return (a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y);
}

ll get_max(node *a,long n)//a是凸包
{	ll ans=0;
	if(n==2)
		return dis(a[1],a[2]);
	a[++n]=a[1];
	for(long i=1,j=3;i<n;i++){
		while(chaji(a[i],a[i+1],a[j])<=chaji(a[i],a[i+1],a[j%n+1]))
			j=j%n+1;
		ans=max(ans,max(dis(a[i],a[j]),dis(a[i+1],a[j])));
	}
	return ans;
}

平面最近点对

x x x排序,记录当前最小距离 h h h,在 x n o w − h → x n o w x_{now}-h \to x_{now} xnowhxnow的范围内更新答案,区间内按照 y y y排序

计算几何模板

基本函数与常量

const double eps=1e-10;
const double pi=acos(-1);
inline int sgn(double x){return fabs(x)<eps?0:(x<0?-1:1);}

二维点类

struct point{
	double x,y;
	point(double a=0,double b=0):x(a),y(b){}
	point operator +(const point &A)const{
		return (point){x+A.x,y+A.y};
	}
	point operator -(const point &A)const{
		return (point){x-A.x,y-A.y};
	}
	point operator *(const double v)const{
		return (point){x*v,y*v};
	}
	point operator /(const double v)const{
		return (point){x/v,y/v};
	}
	bool operator ==(const point &A)const{
		return sgn(x-A.x)==0&&sgn(y-A.y)==0;
	}
	double norm(){
		return sqrt(x*x+y*y);
	}
};

点积

double dot(point a,point b){
	return a.x*b.x+a.y*b.y;
}

叉积

double det(point a,point b){
	return a.x*b.y-a.y*b.x;
}

两点之间距离

double dist(point a,point b){
	return (a-b).norm();
}

点绕原点逆时针旋转(弧度)

点p绕原点O逆时针旋转弧度A: r o t a t e _ p o i n t ( p , A ) rotate\_point(p,A) rotate_point(p,A)

点a绕点b逆时针旋转弧度c: r o t a t e _ p o i n t ( a − b , c ) + b rotate\_point(a-b,c)+b rotate_point(ab,c)+b

point rotate_point(point p,double A){
	return (point){p.x*cos(A)-p.y*sin(A),p.x*sin(A)+p.y*cos(A)};
}

二维线段直线类

struct line{
	point a,b;
	line(){}
	line(point x,point y):a(x),b(y){}
};

点到直线的距离

double point_to_line(point p,point s,point t){
	return fabs(det(s-p,t-p)/dist(s,t));
}

点到线段的距离

double point_to_segment(point p,point s,point t){
	if(sgn(dot(p-s,t-s))==-1)return dist(p,s);
	if(sgn(dot(p-t,s-t))==-1)return dist(p,t);
	return fabs(det(s-p,t-p)/dist(s,t));
}

点在直线上的垂足

point point_proj_line(point p,point s,point t){
	double r=dot(t-s,p-s)/dot(t-s,t-s);
	return s+(t-s)*r;
}

判断点在直线上

bool point_on_line(point p,point s,point t){
	return sgn(det(p-s,p-t))==0;
}

判断点在线段上

包含端点,不含端点则将<=改成<

bool point_on_segment(point p,point s,point t){
	return sgn(det(p-s,p-t))==0&&sgn(dot(p-s,p-t))<=0;
}

判断直线平行

bool parallel(line a,line b){
	return sgn(det(a.a-a.b,b.a-b.b))==0;
}

两条直线的交点

如果有交点则返回true,且交点保存在res中

需要注意的是,两直线重合也返回false

bool line_make_point(line a,line b,point &res){
	if(parallel(a,b))return 0;
	double s1=det(a.a-b.a,b.b-b.a);
	double s2=det(a.b-b.a,b.b-b.a);
	res=(a.b*s1-a.a*s2)/(s1-s2);
	return 1;
}

若两直线不平行,以下代码直接返回交点

point line_make_point(line a,line b){
	double s1=det(a.a-b.a,b.b-b.a);
	double s2=det(a.b-b.a,b.b-b.a);
	return (a.b*s1-a.a*s2)/(s1-s2);
}

三角形的有向面积(需*0.5)

double cross(point o,point a,point b){//*0.5
	return (a.x-o.x)*(b.y-o.y)-(a.y-o.y)*(b.x-o.x);
}

判断直线和线段是否相交

判断直线a和线段b是否相交

1表示规范相交(只有一个交点且线段端点不在直线上)

2表示不规范相交(线段的一个或两个端点在直线上)

0表示不相交

int line_and_segment(line a,line b){
	int d1=sgn(cross(a.a,a.b,b.a));
	int d2=sgn(cross(a.a,a.b,b.b));
	if((d1^d2)==-2)return 1;
	if(d1==0||d2==0)return 2;
	return 0;
}

判断线段和线段是否相交

bool segment_and_segment(line a,line b){
	if(point_on_line(a.a,b.a,b.b)&&point_on_line(a.b,b.a,b.b))//四点共线
		return point_on_segment(a.a,b.a,b.b)||point_on_segment(a.b,b.a,b.b)||
			point_on_segment(b.a,a.a,a.b)||point_on_segment(b.b,a.a,a.b);
	return sgn(det(a.b-a.a,b.a-a.a)*det(a.b-a.a,b.b-a.a))<=0&&
		sgn(det(b.b-b.a,a.a-b.a)*det(b.b-b.a,a.b-b.a))<=0;
}

将直线沿法向量方向平移

沿a.a->a.b的左侧平移距离len

line move_d(line a,double len){
	point e=a.b-a.a;
	e=rotate_point(e/e.norm(),pi/2);
	return (line){a.a+e*len,a.b+e*len};
}

多边形类

多边形顶点坐标按逆时针排序

struct polygon{
    static const int SIZE=1005;
	int n;
	point A[SIZE];
	polygon(){}
};

计算多边形面积

	double area(){
		double res=0;
		A[n+1]=A[1];
		for(int i=1;i<=n;i++)res+=det(A[i],A[i+1]);
		return res/2.0;
	}

计算多边形周长

	double perimeter(){
		double res=0;
		A[n+1]=A[1];
		for(int i=1;i<=n;i++)res+=(A[i]-A[i+1]).norm();
		return res;
	}

判断点是否在多边形内部

	int point_in(point p){
		int res=0;
		A[n+1]=A[1];
		for(int i=1;i<=n;i++){
			if(point_on_segment(p,A[i],A[i+1]))return 2;
			int k=sgn(det(A[i+1]-A[i],p-A[i]));
			int d1=sgn(A[i].y-p.y);
			int d2=sgn(A[i+1].y-p.y);
			if(k>0&&d1>0&&d2<=0)res++;
			if(k<0&&d2>0&&d1<=0)res--;
		}
		return res!=0;
	}

计算多边形的重心

	point mass_center(){
		point res=(point){0,0};
		if(sgn(area())==0)return res;
		A[n+1]=A[1];
		for(int i=1;i<=n;i++)res=res+(A[i]+A[i+1])*det(A[i],A[i+1]);
		return res/area()/6.0;
	}

多边形边界上的格点个数

	int gcd(int a,int b){
		if(b==0)return a;
		return gcd(b,a%b);
	}
	int border_int_point(){
		int res=0;
		A[n+1]=A[1];
		for(int i=1;i<=n;i++)res+=gcd(abs(int(A[i].x-A[i+1].x)),abs(int(A[i].y-A[i+1].y)));
		return res;
	}

多边形内的格点个数

Pick定理:对于顶点均为整点的简单多边形,其内部(不含边界)的格点个数为 a a a,其边界上的格点个数为 b b b,其面积为 S S S

它们满足关系式: S = a + b 2 − 1 S=a+\frac b2 -1 S=a+2b1

变形可得: a = S − b 2 + 1 a=S-\frac b2 +1 a=S2b+1

皮克定理与欧拉公式( V − E + F = 2 V-E+F=2 VE+F=2)等价

	int inside_int_point(){
		return (int)(area())+1-border_int_point()/2;
	}

三角形外接圆圆心

point circumscribed_circle(point a,point b,point c)
{	double x1,x2,x3,y1,y2,y3;
 	x1=a.x;x2=b.x;x3=c.x;
 	y1=a.y;y2=b.y;y3=c.y;
 	double t1=x1*x1+y1*y1;
 	double t2=x2*x2+y2*y2;
 	double t3=x3*x3+y3*y3;
 	double tmp= x1*y2+x2*y3+x3*y1-x1*y3-x2*y1-x3*y2;
    double x = (t2*y3+t1*y2+t3*y1-t2*y1-t3*y2-t1*y3)/tmp/2.;
    double y = (t3*x2+t2*x1+t1*x3-t1*x2-t2*x3-t3*x1)/tmp/2.;
 	return (point){x,y};
}

凸多边形类

struct polygon_convex{
	vector<point>p;
	polygon_convex(int sz=0){
		p.resize(sz);
	}
};

计算凸包面积

	double area(){
		double res=0;
		for(int i=0;i<p.size();i++)res+=det(p[i],p[(i+1)%p.size()]);
		return res/2.0;
	}

计算凸包周长

	double perimeter(){
		double res=0;
		for(int i=0;i<p.size();i++)res+=(p[i]-p[(i+1)%p.size()]).norm();
		return res;
	}

求点集的凸包(逆时针顺序)

polygon_convex convex_hull(vector<point>vi){
	polygon_convex res(2*vi.size()+5);
	sort(vi.begin(),vi.end(),[&](point &a,point &b){
		if(sgn(a.x-b.x)!=0)return a.x<b.x;
		return a.y<b.y;
	});
	vi.erase(unique(vi.begin(),vi.end()),vi.end());
	int m=0;
	for(int i=0;i<vi.size();i++){
		while(m>1&&sgn(det(res.p[m-1]-res.p[m-2],vi[i]-res.p[m-2]))<=0)m--;
		res.p[m++]=vi[i];
	}
	int k=m;
	for(int i=int(vi.size())-2;i>=0;i--){
		while(m>k&&sgn(det(res.p[m-1]-res.p[m-2],vi[i]-res.p[m-2]))<=0)m--;
		res.p[m++]=vi[i];
	}
	res.p.resize(m);
	if(vi.size()>1)res.p.resize(m-1);
	return res;
}

判断点是否在凸包内部

需要凸包逆时针

l o w e r _ b o u n d lower\_bound lower_bound写法,后续补上

bool contain(polygon_convex &a,const point &p)
{	int n=a.p.size(),l=1,r=n-1;
	if(sgn(cross(a.p[0],a.p[l],a.p[r]))>0)swap(l,r);
	//允许边界()>=0||()<=0
	if(sgn(cross(a.p[0],a.p[n-1],p)>0)||sgn(cross(a.p[0],a.p[1],p)<0))return false;
	while(abs(l-r)>1){
		int c=(l+r)/2;
		if(sgn(cross(a.p[0],a.p[c],p))>0)r=c;
		else l=c;
	}
	//允许边界return ()<0
	return sgn(cross(a.p[l],a.p[r],p))<=0;
}

半平面交

P4196

以下代码能求出半平面交所形成的闭合凸包。如果闭合凸包不存在,或者半平面无交集,需要另行判断。

const double eps=1e-10;
const double pi=acos(-1);
inline int sgn(double x){return fabs(x)<eps?0:(x<0?-1:1);}
struct point{
	double x,y;
	point(double a=0,double b=0):x(a),y(b){}
	point operator -(const point &A)const{
		return (point){x-A.x,y-A.y};
	}
	point operator *(const double v)const{
		return (point){x*v,y*v};
	}
	point operator /(const double v)const{
		return (point){x/v,y/v};
	}
};
struct line {
	point a,b;
	line(){}
	line(point x,point y):a(x),b(y){}
};
double det(point a,point b){
	return a.x*b.y-a.y*b.x;
}
point line_make_point(line a,line b){
	double s1=det(a.a-b.a,b.b-b.a);
	double s2=det(a.b-b.a,b.b-b.a);
	return (a.b*s1-a.a*s2)/(s1-s2);
}
struct polygon_convex{
	vector<point>p;
	polygon_convex(int sz=0){
		p.resize(sz);
	}
	double area(){
		double res=0;
		for(int i=0;i<p.size();i++)res+=det(p[i],p[(i+1)%p.size()]);
		return res/2.0;
	}
};
bool on_right(line l1,line l2,line l3){
	point o=line_make_point(l2,l3);
	return sgn(det(l1.b-l1.a,o-l1.a))<0;
}
double angle(line l){
	return atan2(l.b.y-l.a.y,l.b.x-l.a.x);
}
polygon_convex halfplane_intersection(vector<line>vi){
	polygon_convex res;
	sort(vi.begin(),vi.end(),[&](line l1,line l2){
		double ang1=angle(l1);
		double ang2=angle(l2);
		if(sgn(ang1-ang2)==0)return det(l1.b-l1.a,l2.b-l1.a)<0;
		return ang1<ang2;
	});
	static const int SIZE=1e6+5;
	static line Q[SIZE];
	int L=0,R=0;
	for(int i=0;i<vi.size();i++){
		if(i&&(angle(vi[i])-angle(vi[i-1]))==0)continue;
		while(L+1<R&&on_right(vi[i],Q[R-1],Q[R]))R--;
		while(L+1<R&&on_right(vi[i],Q[L+1],Q[L+2]))L++;
		Q[++R]=vi[i];
	}
	while(L+1<R&&on_right(Q[L+1],Q[R-1],Q[R]))R--;
	while(L+1<R&&on_right(Q[R],Q[L+1],Q[L+2]))L++;
	Q[R+1]=Q[L+1];
	for(int i=L+1;i<=R;i++)res.p.push_back(line_make_point(Q[i],Q[i+1]));
	return res;
}

平面最远点对(凸包直径)

double convex_diameter(polygon_convex &a,int &first,int &second){
	vector<point>&vi=a.p;
	int n=vi.size();
	if(n<=1){
		first=second=0;
		return 0.0;
	}
	double res=0;
	for(int i=0,j=0;i<n;i++){
		while((j+1)%n!=i&&sgn(det(vi[(i+1)%n]-vi[i],vi[j]-vi[i])-det(vi[(i+1)%n]-vi[i],vi[(j+1)%n]-vi[i]))<=0)j=(j+1)%n;
		double d=dist(vi[i],vi[j]);
		if(d>res)res=d,first=i,second=j;
		d=dist(vi[(i+1)%n],vi[j]);
		if(d>res)res=d,first=(i+1)%n,second=j;
	}
	return res;
}

闵可夫斯基和

凸包 A , B A,B A,B的闵可夫斯基和 C = { a + b ∣ a ∈ A , b ∈ B } C=\{a+b|a \in A, b \in B\} C={a+baA,bB}

polygon_convex Minkowski(polygon_convex &a,polygon_convex &b)
{	int n=a.p.size(),m=b.p.size(),posa=0,posb=0;
	vector<point>pa,pb,pc;
	for(int i=0;i<n;i++)pa.push_back(a.p[(i+1)%n]-a.p[i]);
	for(int i=0;i<m;i++)pb.push_back(b.p[(i+1)%m]-b.p[i]);
	point now=a.p[0]+b.p[0];
	pc.push_back(now);
	while(posa<n&&posb<m)pc.push_back(now+=sgn(det(pa[posa],pb[posb]))>=0?pa[posa++]:pb[posb++]);
	while(posa<n)pc.push_back(now+=pa[posa++]);
	while(posb<m)pc.push_back(now+=pb[posb++]);
	return convex_hull(pc);
}

三维点类

struct point3{
	double x,y,z;
	point3(double a=0,double b=0,double c=0):x(a),y(b),z(c){}
	point3 operator +(const point3 &A)const{
		return (point3){x+A.x,y+A.y,z+A.z};
	}
	point3 operator -(const point3 &A)const{
		return (point3){x-A.x,y-A.y,z-A.z};
	}
	point3 operator *(const double v)const{
		return (point3){x*v,y*v,z*v};
	}
	point3 operator /(const double v)const{
		return (point3){x/v,y/v,z/v};
	}
	bool operator ==(const point3 &A)const{
		return sgn(x-A.x)==0&&sgn(y-A.y)==0&&sgn(z-A.z)==0;
	}
};

向量长度

	double length(){
		return sqrt(x*x+y*y+z*z);
	}

单位向量

	point3 unit(){
		return *this/length();
	}

点积

double dot(point3 a,point3 b){
	return a.x*b.x+a.y*b.y+a.z*b.z;
}

叉积

point3 det(point3 a,point3 b,point3 c){
	return (point3){a.y*b.z-a.z*b.y,a.z*b.x-a.x*b.z,a.x*b.y-a.y*b.x};
}

混合积

除以6就是a,b,c三个向量所构成的四面体的体积

double mix(point3 a,point3 b,point3 c){
	return dot(a,det(b,c));
}

两点之间距离

double dist(point3 a,point3 b){
	return (a-b).length();
}

三维直线与平面类

struct line3{
	point3 a,b;
	line3(){}
	line3(point3 x,point3 y):a(x),b(y){}
	double length(){
		return (a-b).length();
	}
};
struct plane3{
	point3 a,b,c;
	plane3(){}
	plane3(point3 x,point3 y,point3 z):a(x),b(y),c(z){}
};

平面法向量

point3 normal(point3 a,point3 b,point3 c){
	return det(a-b,b-c);
}
point3 normal(plane3 s){
	return det(s.a-s.b,s.b-s.c);
}

判断三点一线

bool points_one_line(point3 a,point3 b,point3 c){
	return sgn((det(a-b,b-c)).length())==0;
}

判断四点共面

bool points_one_plane(point3 a,point3 b,point3 c,point3 d){
	return sgn(dot(normal(a,b,c),d-a))==0;
}

判断直线平行

bool parallel(line3 l1,line3 l2){
	return sgn(det(l1.a-l1.b,l2.a-l2.b).length())==0;
}

判断直线垂直

bool perpendicular(line3 l1,line3 l2){
	return sgn(dot(l1.a-l1.b,l2.a-l2.b))==0;
}

判断平面内两点在直线同侧

bool same_side(point3 a,point3 b,line3 l){
	return dot(det(l.a-l.b,a-l.b),det(l.a-l.b,b-l.b))>eps;
}

判断平面内两点在直线异侧

bool opposite_side(point3 a,point3 b,line3 l){
	return dot(det(l.a-l.b,a-l.b),det(l.a-l.b,b-l.b))<-eps;
}

判断点在线段上(包含端点)

bool point_on_segment_in(point3 p,line3 l){
	return sgn(det(p-l.a,p-l.b).length())==0&&
		(l.a.x-p.x)*(l.b.x-p.x)<eps&&
		(l.a.y-p.y)*(l.b.y-p.y)<eps&&
		(l.a.z-p.z)*(l.b.z-p.z)<eps;
}

判断点在线段上(不含端点)

bool point_on_segment_ex(point3 p,line3 l){
	return point_on_segment_in(p,l)&&
		(sgn(p.x-l.a.x)||sgn(p.y-l.a.y)||sgn(p.z-l.a.z))&&
		(sgn(p.x-l.b.x)||sgn(p.y-l.b.y)||sgn(p.z-l.b.z));
}

判断线段是否相交(包含端点)

bool segment_intersect_in(line3 l1,line3 l2){
	if(points_one_plane(l1.a,l1.b,l2.a,l2.b)==0)return 0;
	if(points_one_line(l1.a,l1.b,l2.a)==0||points_one_line(l1.a,l1.b,l2.b)==0)
		return same_side(l1.a,l1.b,l2)==0&&same_side(l2.a,l2.b,l1)==0;
	return point_on_segment_in(l1.a,l2)||point_on_segment_in(l1.b,l2)||
		point_on_segment_in(l2.a,l1)||point_on_segment_in(l2.b,l1);
}

判断线段是否相交(不含端点)

注意:当线段共线时输出0

bool segment_intersect_ex(line3 l1,line3 l2){
	return points_one_plane(l1.a,l1.b,l2.a,l2.b)&&opposite_side(l1.a,l1.b,l2)&&opposite_side(l2.a,l2.b,l1);
}

两条直线的交点(需保证相交)

point3 line_make_point(line3 l1,line3 l2){
	double t=((l1.a.x-l2.a.x)*(l2.a.y-l2.b.y)-(l1.a.y-l2.a.y)*(l2.a.x-l2.b.x))
		/((l1.a.x-l1.b.x)*(l2.a.y-l2.b.y)-(l1.a.y-l1.b.y)*(l2.a.x-l2.b.x));
	return l1.a+(l1.b-l1.a)*t;
}

点到直线的距离

double point_to_line(point3 p,line3 l){
	return det(p-l.a,l.b-l.a).length()/l.length();
}

直线到直线的距离(不平行)

double line_to_line(line3 l1,line3 l2){
	point3 n=det(l1.a-l1.b,l2.a-l2.b);
	return fabs(dot(l1.a-l2.a,n))/n.length();
}

两条直线的夹角的cos值

double angle_cos(line3 l1,line3 l2){
	return dot(l1.a-l1.b,l2.a-l2.b)/(l1.a-l1.b).length()/(l2.a-l2.b).length();
}

点绕向量逆时针旋转(弧度)

点p绕向量Ol逆时针旋转弧度A:rotate_point(p,l,A)

点p绕直线l逆时针旋转弧度A:rotate_point(p-l.a,l.b-l.a,A)+l.a

point3 rotate_point(point3 p,point3 l,double A){
	l=l.unit();
	return p*cos(A)+det(l,p)*sin(A)+l*dot(l,p)*(1-cos(A));
}

判断两点在平面同侧

bool same_side(point3 a,point3 b,plane3 s){
	return dot(normal(s),a-s.a)*dot(normal(s),b-s.a)>eps;
}

判断两点在平面异侧

bool opposite_side(point3 a,point3 b,plane3 s){
	return dot(normal(s),a-s.a)*dot(normal(s),b-s.a)<-eps;
}

判断直线与平面平行

bool parallel(line3 l,plane3 s){
	return sgn(dot(l.a-l.b,normal(s)))==0;
}

判断两平面平行

bool parallel(plane3 s1,plane3 s2){
	return sgn(det(normal(s1),normal(s2)).length())==0;
}

判断直线与平面垂直

bool perpendicular(line3 l,plane3 s){
	return sgn(det(l.a-l.b,normal(s)).length())==0;
}

判断两平面垂直

bool perpendicular(plane3 s1,plane3 s2){
	return sgn(dot(normal(s1),normal(s2)))==0;
}

判断点在三角形内(包含边界)

bool point_in_plane_in(point3 p,plane3 s){
	return sgn(det(s.a-s.b,s.a-s.c).length()
		-det(p-s.a,p-s.b).length()
		-det(p-s.b,p-s.c).length()
		-det(p-s.c,p-s.a).length())==0;
}

判断点在三角形内(不含边界)

bool point_in_plane_ex(point3 p,plane3 s){
	return point_in_plane_in(p,s)&&
		det(p-s.a,p-s.b).length()>eps&&
		det(p-s.b,p-s.c).length()>eps&&
		det(p-s.c,p-s.a).length()>eps;
}

判断线段与三角形相交(包含边界)

bool segment_plane_in(line3 l,plane3 s){
	return same_side(l.a,l.b,s)==0&&
		same_side(s.a,s.b,(plane3){l.a,l.b,s.c})==0&&
		same_side(s.b,s.c,(plane3){l.a,l.b,s.a})==0&&
		same_side(s.c,s.a,(plane3){l.a,l.b,s.b})==0;
}

判断线段与三角形相交(不含边界)

bool segment_plane_ex(line3 l,plane3 s){
	return opposite_side(l.a,l.b,s)&&
		opposite_side(s.a,s.b,(plane3){l.a,l.b,s.c})&&
		opposite_side(s.b,s.c,(plane3){l.a,l.b,s.a})&&
		opposite_side(s.c,s.a,(plane3){l.a,l.b,s.b});
}

直线与平面的交点

point3 line_plane_make_point(line3 l,plane3 s){
	point3 n=normal(s);
	double t=(n.x*(s.a.x-l.a.x)+n.y*(s.a.y-l.a.y)+n.z*(s.a.z-l.a.z))
		/(n.x*(l.b.x-l.a.x)+n.y*(l.b.y-l.a.y)+n.z*(l.b.z-l.a.z));
	return l.a+(l.b-l.a)*t;
}

两平面的交线

bool plane_make_line(plane3 s1,plane3 s2,line3 &res){
	if(parallel(s1,s2))return 0;
	if(parallel((line3){s2.a,s2.b},s1))res.a=line_plane_make_point((line3){s2.b,s2.c},s1);
	else res.a=line_plane_make_point((line3){s2.a,s2.b},s1);
	res.b=res.a+det(normal(s1),normal(s2));
	return 1;
}
line3 plane_make_line(plane3 s1,plane3 s2){
	line3 res;
	if(parallel((line3){s2.a,s2.b},s1))res.a=line_plane_make_point((line3){s2.b,s2.c},s1);
	else res.a=line_plane_make_point((line3){s2.a,s2.b},s1);
	res.b=res.a+det(normal(s1),normal(s2));
	return res;
}

点到面的距离

double point_to_plane(point3 p,plane3 s){
	return fabs(dot(normal(s),p-s.a))/normal(s).length();
}

两平面的夹角的cos值

需要注意,区分正负

double angle_cos(plane3 s1,plane3 s2){
	return dot(normal(s1),normal(s2))/normal(s1).length()/normal(s2).length();
}

直线与平面的夹角的sin值

需要注意,区分正负

double angle_sin(line3 l,plane3 s){
	return dot(l.a-l.b,normal(s))/(l.a-l.b).length()/normal(s).length();
}

圆类

struct circle{
	point o;
	double r;
	circle(){}
	circle(point p,double r):p(p),r(r){}
	double area(){
		return pi*r*r;
	}
};
inline double mysqrt(double x){return sqrt(max(0.0,x));}

判断圆的关系

int cmp(double a,double b){return fabs(a-b)<=eps?0:(a<b)?-1:1;}
int relation(const circle &a,const circle &b)//2内含,1内切,0相交,-1外切,-2相离
{
    double d=dist(a.o,b.o);
    int f1=cmp(d,fabs(a.r-b.r)),f2=cmp(d,a.r+b.r);
    if(f1<0)return 2;
    if(f1==0)return 1;
    if(f2<0)return 0;
    if(f2==0)return 1;
    return 2;
}

圆的面积交

double get_intersection_area(circle a,circle b)
{
	if(a.r<b.r)swap(a,b);
	double d=dist(a.o,b.o),r1=a.r,r2=b.r;
	if(d>r1+r2)//相离
		return 0;
	else if(d<r1-r2)//a包含b
		return b.area();
	else{
		double x=(d*d+r1*r1-r2*r2)/(2*d);
		double t1=acos(x/r1);
		double t2=acos((d-x)/r1);
		return r1*r1*t1+r2*r2*t2-d*r1*sin(t1);
	}
}

圆与直线交点

inline double mysqrt(double x){return sqrt(max(0.0,x));}
vector<point>circle_line_make_point(point a,point b,point o,double r){
	double dx=b.x-a.x,dy=b.y-a.y;
	double A=dx*dx+dy*dy;
	double B=2*dx*(a.x-o.x)+2*dy*(a.y-o.y);
	double C=(a.x-o.x)*(a.x-o.x)+(a.y-o.y)*(a.y-o.y)-r*r;
	double delta=B*B-4*A*C;
	vector<point>vi;
	if(sgn(delta)>=0){
		double t1=(-B+mysqrt(delta))/(2*A);
		double t2=(-B-mysqrt(delta))/(2*A);
		vi.push_back(point(a.x+t1*dx,a.y+t1*dy));
		if(sgn(delta)>0)vi.push_back(point(a.x+t2*dx,a.y+t2*dy));
	}
	return vi;
}

最小圆覆盖

平面上 n n n个点,求一个半径最小的圆,能覆盖所有的点

时间复杂度期望为 O ( n ) O(n) O(n)

struct point{
	double x,y;
	point(double a=0,double b=0):x(a),y(b){}
	point operator +(const point &A)const{
		return point(x+A.x,y+A.y);
	}
	point operator -(const point &A)const{
		return point(x-A.x,y-A.y);
	}
	point operator /(const double v)const{
		return point(x/v,y/v);
	}
	double norm2(){
		return x*x+y*y;
	}
};
point circle_center(point a,point b,point c){
	double a1=b.x-a.x,b1=b.y-a.y,c1=(a1*a1+b1*b1)/2;
	double a2=c.x-a.x,b2=c.y-a.y,c2=(a2*a2+b2*b2)/2;
	double d=a1*b2-a2*b1;
	return point(a.x+(c1*b2-c2*b1)/d,a.y+(a1*c2-a2*c1)/d);
}
void min_circle_cover(int n,point A[],point &o,double &r){
    srand(time(NULL));
	random_shuffle(A+1,A+n+1);
	o=point(0,0),r=0.0;
	for(int i=1;i<=n;i++)if((A[i]-o).norm2()>r){
		o=A[i],r=0;
		for(int j=1;j<i;j++)if((A[j]-o).norm2()>r){
			o=(A[i]+A[j])/2,r=(A[j]-o).norm2();
			for(int k=1;k<j;k++)if((A[k]-o).norm2()>r)
				o=circle_center(A[i],A[j],A[k]),r=(A[k]-o).norm2();
		}
	}
	r=sqrt(r);
}

圆的扫描线

二维平面上互不相交的圆,按照包含关系建成一颗树

struct circle{
	int x,y,r;
};
int cur;
struct node{
	int x,y,r,id,val;
	node(){}
	node(int x,int y,int r,int id,int val):x(x),y(y),r(r),id(id),val(val){}
	double gety()const
	{
		return y+val*sqrt(sqr(1ll*r)-sqr(1ll*(x-cur)));
	}
	bool operator<(const node &b)const
	{
		if(id==b.id)return val<b.val;
		return gety()<b.gety();
	}
};
vector<int>mp[def];
int fa[def];

void solve(circle *c,int n)
{
	for(int i=0;i<=n;i++){
		fa[i]=0;
		mp[i].clear();
	}
	c[0]={0,0,inf};
	vector<pii>v;
	for(int i=0;i<=n;i++){
		v.emplace_back(c[i].x-c[i].r,i);
		v.emplace_back(c[i].x+c[i].r,i);
	}
	sort(v.begin(),v.end());
	set<node>st;st.clear();
	for(auto i:v){
		int id=i.second;
		int x=c[id].x,y=c[id].y,r=c[id].r;
		cur=i.first;
		if(cur==x-r){//左端点
			if(id){
				auto nxt=st.lower_bound(node(x,y,r,-1,0));
				int ff;
				if(nxt->val==1)ff=nxt->id;
				else ff=fa[nxt->id];
				fa[id]=ff;
				mp[ff].push_back(id);
			}
			st.insert(node(x,y,r,id,1));
			st.insert(node(x,y,r,id,-1));
		}else{//右端点
			st.erase(node(x,y,r,id,1));
			st.erase(node(x,y,r,id,-1));
		}
	}
	assert(st.empty());
}

球类

struct sphere{
	point o;
	double r;
    double volume(){
        return 4./3.*pi*r*r*r;
    }
};

球的体积交

double get_instersetion_volume(sphere s1,sphere s2)
{
	double ans=0;
	double d=dist(s1.o,s2.o);
	double r1=s1.r,r2=s2.r;
	double tmp1=r1-(sqr(r1)-sqr(r2)+sqr(d))/(2*d);
	double V1=pi/3*sqr(tmp1)*(3*r1-tmp1);
	double tmp2=r2-(sqr(r2)-sqr(r1)+sqr(d))/(2*d);
	double V2=pi/3*sqr(tmp2)*(3*r2-tmp2);
	if(d-eps<=r1-r2)//s1完全包含s2
		return s2.volume();
	else if(d-eps<=r2-r1)//s2完全包含s1
		return s1.volume();
	else if(d-eps<=r1+r2)//s1和s2相交
		return V1+V2;
	return 0;
}

其他知识或技巧

三角形重心、外心、垂心、内心

point triangle_mass_center(point a,point b,point c){//重心
	return (a+b+c)/3;
}
point triangle_circle_center(point a,point b,point c){//外心
	double a1=b.x-a.x,b1=b.y-a.y,c1=(a1*a1+b1*b1)/2;
	double a2=c.x-a.x,b2=c.y-a.y,c2=(a2*a2+b2*b2)/2;
	double d=a1*b2-a2*b1;
	return (point){a.x+(c1*b2-c2*b1)/d,a.y+(a1*c2-a2*c1)/d};
}
point triangle_orthocenter(point a,point b,point c){//垂心
	return triangle_mass_center(a,b,c)*3-triangle_circle_center(a,b,c)*2;
}
point triangle_innercenter(point a,point b,point c){//内心
	double la=(b-c).norm();
	double lb=(c-a).norm();
	double lc=(a-b).norm();
	return (point){(la*a.x+lb*b.x+lc*c.x)/(la+lb+lc),(la*a.y+lb*b.y+lc*c.y)/(la+lb+lc)};
}

四面体体积

给定四面体六条棱的长度,求四面体的体积

已知四面体棱 a b , a c , a d , b c , b d , c d ab,ac,ad,bc,bd,cd ab,ac,ad,bc,bd,cd 的长度分别为 l , n , a , m , b , c l,n,a,m,b,c l,n,a,m,b,c

double tetrahedral_volume(double l,double n,double a,double m,double b,double c){
	double x,y;
	x=4*a*a*b*b*c*c-a*a*(b*b+c*c-m*m)*(b*b+c*c-m*m)-b*b*(c*c+a*a-n*n)*(c*c+a*a-n*n);
	y=c*c*(a*a+b*b-l*l)*(a*a+b*b-l*l)-(a*a+b*b-l*l)*(b*b+c*c-m*m)*(c*c+a*a-n*n);
	return sqrt(x-y)/12;
}

图论

树哈希

h a s h n o w = 1 + ∑ h a s h s o n ( n o w , i ) × p r i m e ( s i z e s o n ( n o w , i ) ) hash_{now}=1+\sum hash_{son(now,i)} \times prime(size_{son(now,i)}) hashnow=1+hashson(now,i)×prime(sizeson(now,i))

注意:先判断 s i z e n o w size_{now} sizenow再判断 h a s h n o w hash_{now} hashnow

最短路

Dijkstra O ( n + m log ⁡ m ) O(n+m\log m) O(n+mlogm)

struct node{
	int to;
	ll len;
};
vector<node>mp[def];
bool vis[def];
ll dis[def];

void add(int x,int y,ll z)
{
	mp[x].push_back({y,z});
	mp[y].push_back({x,z});
}

void init(int n)
{
	for(int i=1;i<=n;i++){
		vis[i]=false;
		dis[i]=INF/2;
        mp[i].clear();
	}
}

void dij(int s)
{
	priority_queue<pair<ll,int>,vector<pair<ll,int> >,greater<pair<ll,int> > >que;
	dis[s]=0;
	que.push({dis[s],s});
	while(!que.empty()){
		auto now=que.top().second;
		que.pop();
		if(vis[now])continue;
		vis[now]=true;
		for(auto i:mp[now])
			if(dis[i.to]>dis[now]+i.len){
				dis[i.to]=dis[now]+i.len;
				que.push({dis[i.to],i.to});
			}
	}
}

Johnson全源最短路 O ( n 2 l o g n + n m ) O(n^2logn+nm) O(n2logn+nm)

struct node{
	int to;
	ll len;
};
vector<node>mp[def];
bool vis[def];
int times[def],n;
ll diss[def][def],h[def],dis[def];

bool spfa(int s)
{
	queue<int>que;
	for(int i=0;i<=n;i++){
		h[i]=inf;
		vis[i]=false;
		times[i]=0;
	}
	h[s]=0;
	vis[s]=true;
	times[s]++;
	que.push(s);
	while(!que.empty()){
		auto now=que.front();
		que.pop();
		vis[now]=false;
		for(auto i:mp[now])
			if(h[i.to]>h[now]+i.len){
				h[i.to]=h[now]+i.len;
				if(!vis[i.to]){
					vis[i.to]=true;
					que.push(i.to);
					if(++times[i.to]>n)return false;
				}
			}
	}
	return true;
}

void dij(int s)
{
	priority_queue<pair<ll,int>,vector<pair<ll,int> >,greater<pair<ll,int> > >que;
	for(int i=1;i<=n;i++){
		dis[i]=inf;
		vis[i]=false;
	}
	dis[s]=0;
	que.push({0,s});
	while(!que.empty()){
		auto now=que.top();
		que.pop();
		if(vis[now.second])continue;
		vis[now.second]=true;
		for(auto i:mp[now.second])
			if(dis[i.to]>dis[now.second]+i.len){
				dis[i.to]=dis[now.second]+i.len;
				que.push({dis[i.to],i.to});
			}
	}
}

bool Johnson()//有负环false,无负环true
{
	for(int i=1;i<=n;i++)mp[0].push_back({i,0});
	if(!spfa(0))return false;
	for(int i=1;i<=n;i++)
		for(auto &j:mp[i])
			j.len+=h[i]-h[j.to];
	for(int i=1;i<=n;i++){
		dij(i);
		for(int j=1;j<=n;j++)
			if(dis[j]<inf)diss[i][j]=dis[j]+h[j]-h[i];
			else diss[i][j]=dis[j];
	}
	return true;
}

生成树

最小生成树

struct node{
    int x,y,z;
}a[def];
int fa[def];

void init(int n,int m)
{
    for(int i=1;i<=n;i++)fa[i]=i;
    sort(a+1,a+m+1,[&](node a,node b){return a.z<b.z;});
}

int find(int x){return fa[x]==x?x:fa[x]=find(x);}

ll solve(int m)
{	ll ans=0;
    for(int i=1;i<=m;i++)
        if(find(a[i].x)!=find(a[i].y)){
            ans+=a[i].z;
            fa[find(x)]=find(y);
        }
}

生成树计数(矩阵树定理)

无向图统计生成树个数

matrix ma;

void init(int n)
{
    for(int i=1;i<=n;i++)
        for(auto j:mp[i])
            ma.a[i][j]=(ma.a[i][j]-1+MOD)%MOD;
}

ll calc(int n)
{
    return ma.calc(n-1);
}

LCA

倍增LCA O ( n log ⁡ n ) O(n\log n) O(nlogn)

#define bit 31
vector<int>mp[def];
int dep[def],fa[def][bit],cnt;

void init(int root)
{
    dep[root]=1;
    for(int i=0;i<bit;i++)
        fa[root][i]=0;
}

void dfs(int now)
{
	for(int i=1;i<bit;i++)
		fa[now][i]=fa[fa[now][i-1]][i-1];
	for(auto i:mp[now])
		if(i!=fa[now][0]){
			dep[i]=dep[now]+1;
			dfs(i);
		}
}

int lca(int x,int y)
{
	if(dep[x]<dep[y])
		swap(x,y);
	for(int i=bit-1;i>=0;i--)
		if(dep[fa[x][i]]>=dep[y])
			x=fa[x][i];
	for(int i=bit-1;i>=0;i--)
		if(fa[x][i]!=fa[y][i]){
			x=fa[x][i];
			y=fa[y][i];
		}
    return x==y?x:fa[x][0];
}

tarjan LCA O ( n + m ) O(n+m) O(n+m)

本质上是个树形dp

vector<pair<int,int>>qu[def];
int fa[def];
bool vis[def];

int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);}

void tarjan(int now,int pre)
{
	vis[now]=false;
	fa[now]=now;
	for(auto i:mp[now])if(i!=pre)tarjan(i,now);
	for(auto i:qu[now])if(vis[i.first])ans[i.second]=find(i.first);
	vis[now]=true;
	if(pre)fa[now]=pre;
}

双连通分量

若一个无向连通图不存在割点,则称它为“点双连通图”。若一个无向连通图不存在割边,则称它为“边双连通图”。
无向图的极大点双连通子图称为“点双连通分量”,简记为“v-DCC”。无向连通图的极大边双连通子图被称为“边双连通分量”,简记为“e-DCC”。二者统称为“双连通分量”,简记为“DCC”。

点双连通分量(广义圆方树)

一个点双一个方点

vector<int>mp[def],T[def];
int cnt,cnt1,dfn[def],low[def],sta1[def],topp;
bool vis[def],ge[def];

void tarjan(int now,int pre)
{	int son=0;
	dfn[now]=low[now]=++cnt;
	vis[now]=true;
	sta1[++topp]=now;
	for(auto i:mp[now])if(i!=pre){
		if(!dfn[i]){
			tarjan(i,now);
			low[now]=min(low[now],low[i]);
			son++;
			if((!pre&&son>1)||(pre&&dfn[now]<=low[i]))ge[now]=true;
			if(low[i]>=dfn[now]){
				T[++cnt1].push_back(now);
				T[now].push_back(cnt1);
				do{
					T[cnt1].push_back(sta1[topp]);
					T[sta1[topp]].push_back(cnt1);
				}while(sta1[topp--]!=i);
			}
		}else if(vis[i]){
			low[now]=min(low[now],dfn[i]);
		}
	}
}

边双连通分量(强连通分量)

核心:没有割边

解法:tarjan缩完点之后重构图,剩下的边就是割边,缩的点就是边双连通分量

stack<int>sta;
int dfn[def],low[def],size[def],id[def],cnt,num;
bool vis[def];

void tarjan(int now)//一次遍历一个连通图,无向图加个pre
{
	vis[now]=true;
	dfn[now]=low[now]=++cnt;
	sta.push(now);
	for(auto i:mp[now])
		if(!dfn[i]){
			tarjan(i);
			low[now]=min(low[now],low[i]);
		}else if(vis[i]){
			low[now]=min(low[now],dfn[i]);
		}
	if(low[now]==dfn[now]){
		size[++num]=0;
		int top;
		do{
			top=sta.top();sta.pop();
			vis[top]=false;
			id[top]=num;
			size[num]+=a[top];
		}while(top!=now);
	}
}

2-SAT

定义: n n n b o o l bool bool变量 x 1 . . . x n x_1...x_n x1...xn,要满足 m m m个条件,每个条件含有 x i = 0 / 1   o r   x j = 0 / 1 x_i=0/1\ or\ x_j=0/1 xi=0/1 or xj=0/1,求满足所有条件的一种方案

解法:拆点,每个点拆为1点和0点,条件 a ∨ b a\lor b ab可以转换为 ¬ a → b ∧ ¬ b → a \lnot a\to b \land \lnot b\to a ¬ab¬ba,按着建图,如果 a a a ¬ a \lnot a ¬a连通,则无解

	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++){
		scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
		mp[x1+n*y1].push_back(x2+n*!y2);
		mp[x2+n*y2].push_back(x1+n*!y1);
	}
	for(int i=1;i<=n*2;i++)if(!dfn[i])tarjan(i);
	bool t=true;
	for(int i=1;i<=n;i++)if(id[i]==id[i+n])t=false;
	if(!t){puts("IMPOSSIBLE");return 0;}
	puts("POSSIBLE");
	for(int i=1;i<=n;i++)printf("%d ",id[i]<id[i+n]);
	puts("");

二分图

匈牙利算法 O ( n 3 ) O(n^3) O(n3)

bool vis[def],mp[def][def];
int p[def],n,m;

bool dfs(int now)
{
	for(int i=1;i<=m;i++)
		if(!vis[i]&&mp[now][i]){
			vis[i]=true;
			if(p[i]==-1||dfs(p[i])){
				p[i]=now;
				return true;
			}
		}
	return false;
}

int solve()
{	int ans=0;
	memset(p,-1,sizeof(p));
	for(int i=1;i<=n;i++){
		memset(vis,false,sizeof(vis));
		if(dfs(i))
			ans++;
	}
	return ans;
}

Hopcroft-karp O ( n m ) O(n\sqrt m) O(nm )

const int Mm=5e5+5,Mn=5e5+5;
const int INF=0x3f3f3f3f;
struct edge {
    int v,next;
}e[Mm];
int tot,head[Mn];
void add(int u,int v) {
    e[tot].v=v;
    e[tot].next=head[u];
    head[u]=tot++;
}
int mx[Mn],my[Mn],vis[Mn];
int dis;
int dx[Mn],dy[Mn];
int n;
bool searchp() {
    queue<int>q;
    dis=INF;
    memset(dx,-1,sizeof(dx));
    memset(dy,-1,sizeof(dy));
    for(int i=1;i<=n;i++) {
        if(mx[i]==-1) {
            q.push(i);
            dx[i]=0;
        }
    }
    while(!q.empty()) {
        int u=q.front();
        q.pop();
        if(dx[u]>dis) break;
        for(int i=head[u];~i;i=e[i].next) {
            int v=e[i].v;
            if(dy[v]==-1) {
                dy[v]=dx[u]+1;
                if(my[v]==-1) dis=dy[v];
                else {
                    dx[my[v]]=dy[v]+1;
                    q.push(my[v]);
                }
            }
        }
    }
    return dis!=INF;
}
bool dfs(int u) {
    for(int i=head[u];~i;i=e[i].next) {
        int v=e[i].v;
        if(vis[v]||(dy[v]!=dx[u]+1)) continue;
        vis[v]=1;
        if(my[v]!=-1&&dy[v]==dis) continue;
        if(my[v]==-1||dfs(my[v])) {
            my[v]=u;
            mx[u]=v;
            return true;
        }
    }
    return false;
}
int maxMatch(int n) {
    int res = 0;
    memset(mx,-1,sizeof(mx));
    memset(my,-1,sizeof(my));
    while(searchp()) {
        memset(vis,0,sizeof(vis));
        for(int i=1;i<=n; i++)
            if(mx[i] == -1 && dfs(i))
                res++;
    }
    return res;
}
void init() {
    tot=0;
    memset(head,-1,sizeof(head));
}

KM O ( n 3 ) O(n^3) O(n3)

ll mp[def][def],c[def],ka[def],kb[def];
int link[def],p[def],n;
bool vis[def];

void init()
{
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            mp[i][j]=INT64_MIN/3;
}
void add(int x,int y,ll z){Max(mp[x][y],z);}

void bfs(int x)
{	int a,now=0,nxt=0;
	ll minn;
	for(int i=1;i<=n;i++)p[i]=0,c[i]=inf;
	link[now]=x;
	do{
		a=link[now];minn=INT64_MAX;vis[now]=true;
		for(int i=1;i<=n;i++)if(!vis[i]){
			if(c[i]>ka[a]+kb[i]-mp[a][i])
				c[i]=ka[a]+kb[i]-mp[a][i],p[i]=now;
			if(c[i]<minn)minn=c[i],nxt=i;
		}
		for(int i=0;i<=n;i++)
			if(vis[i])ka[link[i]]-=minn,kb[i]+=minn;
			else c[i]-=minn;
		now=nxt;
	}while(link[now]);
	for(;now;now=p[now])link[now]=link[p[now]];
}

ll KM()
{
	for(int i=1;i<=n;i++)link[i]=ka[i]=kb[i]=0;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++)vis[j]=false;
		bfs(i);
	}
	ll ans=0;
	for(int i=1;i<=n;i++)ans+=mp[link[i]][i];
	return ans;
}

一般图匹配(带花树)

vector<int>mp[def];
queue<int>que;
int fa[def],pre[def],match[def],vis[def],type[def];
int n,times;

int lca(int x,int y)
{
	times++;
	x=fa[x],y=fa[y];
	while(vis[x]!=times){
		if(x){
			vis[x]=times;
			x=fa[pre[match[x]]];
		}
		swap(x,y);
	}
	return x;
}

void blossom(int x,int y,int l)
{
	while(fa[x]!=l){
		pre[x]=y;
		y=match[x];
		if(type[y]==1){
			type[y]=0;
			que.push(y);
		}
		fa[x]=fa[y]=fa[l];
		x=pre[y];
	}
}

int calc(int s)
{
	for(int i=1;i<=n;i++){
		fa[i]=i;
		type[i]=-1;
	}
	que=queue<int>();
	type[s]=0;
	que.push(s);
	while(!que.empty()){
		auto now=que.front();que.pop();
		for(auto i:mp[now]){
			if(!~type[i]){
				pre[i]=now;
				type[i]=1;
				if(!match[i]){
					for(int to=i,from=now;to;from=pre[to]){
						match[to]=from;
						swap(match[from],to);
					}
					return 1;
				}
				type[match[i]]=0;
				que.push(match[i]);
			}else if(type[i]==0&&fa[now]!=fa[i]){
				int l=lca(now,i);
				blossom(now,i,l);
				blossom(i,now,l);
			}
		}
	}
	return 0;
}

int solve()
{	int ans=0;
	times=0;
	for(int i=1;i<=n;i++)match[i]=vis[i]=0;
	for(int i=n;i>=1;i--)
		if(!match[i])ans+=calc(i);
	return ans;
}

最大团

bool mp[def][def];
int ans,n,num[def];

bool dfs(int len,int *a,int cnt)
{	int i,j,cnt2,b[def];
	if(len>ans){
		ans=len;
		return true;
	}
	for(i=1;i<=cnt;i++){
		if(len+n-a[i]+1<=ans||len+num[a[i]]<=ans)break;
		cnt2=0;
		for(j=i+1;j<=cnt;j++)
			if(mp[a[i]][a[j]])
				b[++cnt2]=a[j];
		if(dfs(len+1,b,cnt2))
			return true;
	}
	return false;
}

int solve()
{	int i,j,a[def],cnt;
	ans=num[n]=1;
	for(i=n-1;i>=1;i--){
		cnt=0;
		for(j=i+1;j<=n;j++)
			if(mp[i][j])
				a[++cnt]=j;
		dfs(1,a,cnt);
		num[i]=ans;
	}
	return ans;
}

极大团

bool mp[def][def];
int ans,all[def][def],some[def][def],none[def][def];

void dfs(int len,int an,int sn,int nn)
{	int now,sn2,nn2;
	if(!sn&&!nn)ans++;//记录极大团个数
	//if(ans>1000)return;
	now=some[len][1];
	for(int j=1;j<=sn;j++){
		int &next=some[len][j];
		if(mp[now][next])continue;
		sn2=nn2=0;

		for(int i=1;i<=an;i++)
			all[len+1][i]=all[len][i];
		all[len+1][an+1]=next;

		for(int i=1;i<=sn;i++)
			if(mp[next][some[len][i]])some[len+1][++sn2]=some[len][i];

		for(int i=1;i<=nn;i++)
			if(mp[next][none[len][i]])none[len+1][++nn2]=none[len][i];

		dfs(len+1,an+1,sn2,nn2);
		none[len][++nn]=next;
		next=0;
	}
}

网络流

最大流(dicnic)

namespace Dicnic{
	struct node{
		int next,to;
		ll flow;
	}mp[def*10];
	int cnt,tot,head[def],cur[def],dep[def];
	ll ans_flow;

	void range(int l,int r)
	{
		for(int i=l;i<=r;i++)head[i]=-1;
		tot=r;
	}

	void init(int n)
	{
		range(0,n);
		cnt=ans_flow=0;
	}

	void add(int x,int y,ll z)
	{
		mp[cnt]={head[x],y,z};
		head[x]=cnt++;
		mp[cnt]={head[y],x,0};
		head[y]=cnt++;
        //printf("%d %d %lld\n",x,y,z);
	}

	bool bfs(int s,int t)
	{
		for(int i=0;i<=tot;i++){
			dep[i]=0;
			cur[i]=head[i];
		}
		queue<int>que;
		que.push(s);
		dep[s]=1;
		while(!que.empty()){
			auto now=que.front();
			que.pop();
			for(int i=head[now];~i;i=mp[i].next)
				if(!dep[mp[i].to]&&mp[i].flow){
					dep[mp[i].to]=dep[now]+1;
					que.push(mp[i].to);
				}
		}
		return dep[t];
	}

	ll dfs(int now,int t,ll flow)
	{	ll sum=0;
		if(now==t||!flow)
			return flow;
		for(int &i=cur[now];~i;i=mp[i].next)
			if(dep[mp[i].to]==dep[now]+1&&mp[i].flow){
				ll dis=dfs(mp[i].to,t,min(flow,mp[i].flow));
				mp[i].flow-=dis;
				mp[i^1].flow+=dis;
				sum+=dis;
				flow-=dis;
				if(!flow)break;
			}
		return sum;
	}

	void dicnic(int s,int t)
	{
		while(bfs(s,t))
			ans_flow+=dfs(s,t,inf);
	}
}

zkw费用流(spfa)

namespace zkw{
	struct node{
		int next,to;
		ll flow,cost;
	}mp[def*10];
	int head[def],cnt,tot;
	ll dis[def],ans_cost,ans_flow;
	bool vis[def];

	void init(int n)
	{
	    for(int i=0;i<=n;i++)head[i]=-1;
		cnt=0;
		tot=n;
	}

	void add(int x,int y,ll flow,ll cost)
	{
		mp[cnt]={head[x],y,flow,cost};
		head[x]=cnt++;
		mp[cnt]={head[y],x,0,-cost};
		head[y]=cnt++;
	}

	bool spfa(int s,int t)
	{
		deque<int>que;
		for(int i=0;i<=tot;i++){//点编号范围
			vis[i]=false;
			dis[i]=inf;
		}
		que.push_back(t);
		dis[t]=0;
		vis[t]=true;
		while(!que.empty()){
			auto now=que.front();
			que.pop_front();
			vis[now]=false;
			for(int i=head[now];~i;i=mp[i].next)
				if(mp[i^1].flow&&dis[mp[i].to]>dis[now]+mp[i^1].cost){
					dis[mp[i].to]=dis[now]+mp[i^1].cost;
					if(!vis[mp[i].to]){
						vis[mp[i].to]=true;
						if(!que.empty()&&dis[mp[i].to]<dis[que.front()])
							que.push_front(mp[i].to);
						else
							que.push_back(mp[i].to);
					}
				}
		}
		return dis[s]<inf;
	}

	ll dfs(int now,int t,ll flow)
	{	ll sum=0;
		vis[now]=true;
		if(now==t||!flow)
			return flow;
		for(int i=head[now];~i;i=mp[i].next)
			if(!vis[mp[i].to]&&mp[i].flow&&dis[mp[i].to]+mp[i].cost==dis[now]){
				ll len=dfs(mp[i].to,t,min(flow,mp[i].flow));
				mp[i].flow-=len;
				mp[i^1].flow+=len;
				ans_cost+=len*mp[i].cost;
				sum+=len;
				flow-=len;
				if(!flow)break;
			}
		return sum;
	}

	void zkw(int s,int t)
	{
	    ans_flow=ans_cost=0;
		while(spfa(s,t)){
			do{
	            for(int i=0;i<=tot;i++)vis[i]=false;
				ans_flow+=dfs(s,t,inf);
			}while(vis[t]);
		}
	}
}

zkw费用流( n 2 n^2 n2dij)

struct node{
	int next,to;
	ll flow,cost;
}mp[def*def];
int head[def],cnt,tot;
ll dis[def],ans_cost,ans_flow;
bool vis[def];

void init(int n)
{
	memset(head,-1,sizeof(head));
	cnt=0;
	tot=n;
}

void add(int x,int y,ll flow,ll cost)
{
	mp[cnt]={head[x],y,flow,cost};
	head[x]=cnt++;
	mp[cnt]={head[y],x,0,-cost};
	head[y]=cnt++;
}

bool dij(int s,int t)
{
    for(int i=0;i<=tot;i++){
        vis[i]=false;
        dis[i]=inf;
    }
    dis[t]=0;
    while(1){
        ll minn=inf,pos=-1;
        for(int i=0;i<=tot;i++)
            if(!vis[i]){
                if(dis[i]<minn){
                    minn=dis[i];
                    pos=i;
                }
            }
        if(pos==-1)break;
        vis[pos]=true;
        for(int i=head[pos];~i;i=mp[i].next)if(!vis[mp[i].to])
            if(mp[i^1].flow&&dis[mp[i].to]>dis[pos]+mp[i^1].cost)
                dis[mp[i].to]=dis[pos]+mp[i^1].cost;
    }
    return dis[s]<inf;
}

ll dfs(int now,int t,ll flow)
{	ll sum=0;
	vis[now]=true;
	if(now==t||!flow)
		return flow;
	for(int i=head[now];~i;i=mp[i].next)
		if(!vis[mp[i].to]&&mp[i].flow&&dis[mp[i].to]+mp[i].cost==dis[now]){
			ll len=dfs(mp[i].to,t,min(flow,mp[i].flow));
			mp[i].flow-=len;
			mp[i^1].flow+=len;
			ans_cost+=len*mp[i].cost;
			sum+=len;
			flow-=len;
			if(!flow)
				break;
		}
	return sum;
}

void zkw(int s,int t)
{
    ans_flow=ans_cost=0;
	while(dij(s,t)){
		do{
            for(int i=0;i<=tot;i++)vis[i]=false;
			ans_flow+=dfs(s,t,inf);
		}while(vis[t]);
	}
}

MCMF费用流

namespace MCMF{
	struct node{
		int next,to;
		ll flow,cost;
	}mp[def*10];
	int head[def],pre[def],cnt,tot;
	ll dis[def],ans_cost,ans_flow;
	bool vis[def];

	void init(int n)
	{
	    for(int i=0;i<=n;i++)head[i]=-1;
		cnt=0;tot=n;
		ans_cost=ans_flow=0;
	}

	void add(int x,int y,ll flow,ll cost)
	{
		mp[cnt]={head[x],y,flow,cost};
		head[x]=cnt++;
		mp[cnt]={head[y],x,0,-cost};
		head[y]=cnt++;
	}

	bool spfa(int s,int t)
	{
		for(int i=0;i<=tot;i++){
			dis[i]=inf;
			vis[i]=false;
		}
		dis[s]=0;
		queue<int>que;
		que.push(s);
		while(!que.empty()){
			auto now=que.front();que.pop();
			vis[now]=false;
			for(int i=head[now];~i;i=mp[i].next){
				auto &nxt=mp[i];
				if(nxt.flow&&dis[nxt.to]>dis[now]+nxt.cost){
					dis[nxt.to]=dis[now]+nxt.cost;
					pre[nxt.to]=i^1;
					if(!vis[nxt.to]){
						vis[nxt.to]=true;
						que.push(nxt.to);
					}
				}
			}
		}
		return dis[t]<inf;
	}

	void run(int s,int t)
	{
		while(spfa(s,t)){
			ll flow=inf;
			for(int i=t;i!=s;i=mp[pre[i]].to)
				flow=min(flow,mp[pre[i]^1].flow);
			ans_cost+=flow*dis[t];
			ans_flow+=flow;
			for(int i=t;i!=s;i=mp[pre[i]].to){
				mp[pre[i]].flow+=flow;
				mp[pre[i]^1].flow-=flow;
			}
		}
	}
}

网络流24题

餐巾计划问题(拆点,反向思维)

问题: n ( ≤ 2000 ) n(\le 2000) n(2000)天,每天需要 r i ri ri块干净餐巾,买新餐巾 p p p元,快洗 n 1 n1 n1 f 1 f1 f1元,慢洗 n 2 n2 n2 f 2 f2 f2

核心:把一天拆成开始和结束,开始接受脏餐巾,结束提供干净餐巾

    for(int i=1;i<=n;i++){
        scanf("%d",&x);
        zkw::add(0,i,x,0);//源点提供脏毛巾
        zkw::add(i+n,n*2+1,x,0);//汇点接收干净毛巾
    }
    scanf("%d%d%d%d%d",&p,&n1,&f1,&n2,&f2);
    for(int i=1;i<n;i++)zkw::add(i,i+1,inf,0);//滞留一天
    for(int i=1;i<=n;i++)zkw::add(0,i+n,inf,p);//直接购买干净毛巾
    for(int i=1;i+n1<=n;i++)zkw::add(i,i+n+n1,inf,f1);//快洗
    for(int i=1;i+n2<=n;i++)zkw::add(i,i+n+n2,inf,f2);//慢洗
    zkw::zkw(0,n*2+1);
    printf("%lld\n",zkw::ans_cost);

家园/星际转移问题(分层图)

问题:总共 k ( ≤ 50 ) k(\le 50) k(50)个人, m ( ≤ 20 ) m(\le 20) m(20)艘空间船, n ( ≤ 13 ) n(\le 13) n(13)个空间站,1个地球、月球,空间船在若干个空间站间按顺序移动,每次移动花费1秒,最多载 n u m [ i ] num[i] num[i]人,问把所有人从地球转移到月球的最短时间

核心:按时间分层

    scanf("%d%d%d",&n,&m,&k);
    for(int i=0;i<=n+1;i++)fa[i]=i;
    for(int i=1;i<=m;i++){
        scanf("%d%d",&num[i],&cnt[i]);
        pos[i]=0;
        for(int j=0;j<cnt[i];j++){
            scanf("%d",&mp[i][j]);
            if(!~mp[i][j])mp[i][j]=n+1;
            if(j)merge(mp[i][j],mp[i][j-1]);//并查集维护联通性
        }
    }
    if(find(0)!=find(n+1)){puts("0");return 0;}
    Dicnic::init(n+1);
    for(ans=0;Dicnic::ans_flow<k;ans++){//枚举答案,按时间动态建图
        int l=(ans+1)*(n+2),lst=ans*(n+2);
        Dicnic::range(l,l+n+1);
        for(int i=1;i<=n;i++)Dicnic::add(lst+i,l+i,inf);//空间站自己到自己
        for(int i=1;i<=m;i++){
            Dicnic::add(lst+mp[i][pos[i]],l+mp[i][(pos[i]+1)%cnt[i]],num[i]);
            //运输船的空间站转移
            (++pos[i])%=cnt[i];
        }
        Dicnic::add(lst,l,inf);//上一秒源点到这一秒源点
        Dicnic::add(l+n+1,lst+n+1,inf);//这一秒汇点到上一秒汇点
        Dicnic::dicnic(0,n+1);//直接在残量图上跑
    }
    printf("%d\n",ans);

飞行员配对方案问题(二分图板子)

问题:左边 n n n人,右边 m m m人,问配对方案

软件补丁问题(状压+dij)

问题: n ( ≤ 20 ) n(\le 20) n(20)个错误, m ( ≤ 100 ) m(\le 100) m(100)个补丁,每个补丁有独立修补时间 t i t_i ti,需要 B 1 i B1_i B1i中所有错误出现,且 B 2 i B2_i B2i中都不出现时才可使用,效果是修复 F 1 i F1_i F1i的错误,并产生 F 2 i F2_i F2i的错误,问最短修复所有错误时间。 B 1 i , B 2 i F 1 i , F 2 i B1_i,B2_iF1_i,F2_i B1i,B2iF1i,F2i均为集合。

解法:将错误状压作为点状态,不统一建边,dij中直接枚举补丁。

太空飞行计划问题(最大权闭合子图)

问题:有 m m m个实验, n n n个仪器,做实验需要购买指定的一些仪器,仪器可以重复使用。做一个实验有 c i c_i ci的奖金,购买一个仪器需要 p i p_i pi的费用,问最大收益和方案

解法:最大权闭合子图问题。实验点权为 c i c_i ci,仪器点权为 − p i -p_i pi,转化为实验点连向仪器点的有向图

最大权闭合子图

问题:选择一个点,他指向的所有点必选

解法:原有向图边权设为inf,S连向所有正点权点,所有负点权点连向T,ans=sum(正点权)-最小割

    scanf("%d%d",&m,&n);
    Dicnic::init(n+m+1);
    ll sum=0;
    for(int i=1,x;i<=m;i++){
        scanf("%d",&x);
        sum+=x;
        Dicnic::add(0,i,x);
        getline(cin,ss);
        auto s=ss.c_str();
        while(*s==' ')++s;
        while(sscanf(s,"%d",&x)==1){
            Dicnic::add(i,x+m,inf);
            while(*s&&*s!=' ')++s;
            while(*s==' ')++s;
        }
    }
    for(int i=1,x;i<=n;i++){
        scanf("%d",&x);
        Dicnic::add(i+m,n+m+1,x);
    }
    Dicnic::dicnic(0,n+m+1);
    //不满流的实验必选(不是割边),这些边有正向边,从这些边出去可以到达相关联的实验和仪器,这些也是必选
    for(int i=1;i<=m;i++)if(Dicnic::dep[i])printf("%d ",i);puts("");
    for(int i=1;i<=n;i++)if(Dicnic::dep[i+m])printf("%d ",i);puts("");
    printf("%lld\n",sum-Dicnic::ans_flow);

试题库问题(最小割)

问题:题库中有 n n n道题,每道题属于若干类别 p p p。要求组一张卷子,每种类别需要 a i a_i ai道题,问一种组卷方案

解法:源点到类别连一条 a i a_i ai的边,类别到题库中对应题目一条1边,题目到汇点一条1边。

最小路径覆盖问题(DAG转二分图+最小割)

问题:给定一个DAG,问最少多少条不相交的路径可以覆盖掉所有的点

解法:转成二分图,跑最大流或二分图最大匹配

魔术球问题(数学题转DAG)

问题: n n n根柱子,按 1 , 2 , 3... 1,2,3... 1,2,3...顺序往上放球,要求同一根柱子上任意两个相邻的球的和为完全平方数,问最多放多少个球

解法:如果两个数 x , y x,y x,y满足 x + y x+y x+y是完全平方数,那么连一条 min ⁡ ( x , y ) → max ⁡ ( x , y ) \min(x,y)\to \max(x,y) min(x,y)max(x,y)边,问题转换为DAG上求最小路径覆盖问题。数值的上限可以枚举,动态建图。

    scanf("%d",&n);
    Dicnic::init(1);
    for(ans=1;ans-1-Dicnic::ans_flow<=n;ans++){
        int l=ans<<1,r=ans<<1|1;
        Dicnic::range(ans<<1,ans<<1|1);
        Dicnic::add(0,l,1);
        Dicnic::add(r,1,1);
        for(int i=1;i<=ans+2;i++){
            int j=sqr(i)-ans;
            if(j<=0)continue;
            if(j>=ans)break;
            Dicnic::add(j<<1,r,1);
        }
        Dicnic::dicnic(0,1);
    }
    ans-=2;
    printf("%d\n",ans);
    for(int i=1;i<=ans;i++){
        nxt[i]=0;
        for(int j=Dicnic::head[i<<1];~j;j=Dicnic::mp[j].next)if(!Dicnic::mp[j].flow){
            nxt[i]=Dicnic::mp[j].to>>1;
            pre[nxt[i]]=i;
            break;
        }
    }
    for(int i=1;i<=ans;i++)if(!pre[i]){
        for(int j=i;j;j=nxt[j])printf("%d ",j);
        puts("");
    }

最长不下降子序列问题(拆点,流量限制选择次数)

问题:给定一个数列 x 1 , . . . , x n x_1,...,x_n x1,...,xn,计算1)最长不下降子序列长度 s s s;2)每个点只能用一次,求不同的长度为 s s s的子序列个数;3) x 1 , x n x_1,x_n x1,xn可用多次,求2

解法:经典拆点,用中间的流量来限制每个点可以被选择的次数;问题1使用dp,问题23利用问题1的dp数组,求出以每个点为结尾的最长不下降子序列长度,按照 x i ≤ x j & & d p i + 1 = = d p j x_i \le x_j \&\& dp_i+1==dp_j xixj&&dpi+1==dpj的条件建图,注意一些特判

    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]);
    int cnt=0;
    for(int i=1,pos;i<=n;i++){//经典dp
        if(a[i]>=b[cnt])pos=++cnt;
        else pos=upper_bound(b+1,b+cnt+1,a[i])-b;
        b[pos]=a[i];
        num[i]=pos;
    }
    printf("%d\n",cnt);
    Dicnic::init(n*2+1);
    for(int i=1;i<=n;i++)Dicnic::add(i,i+n,1);//拆点
    for(int i=1;i<=n;i++)
        for(int j=i+1;j<=n;j++)if(num[i]+1==num[j]&&a[i]<=a[j])
            Dicnic::add(i+n,j,1);//按条件建边
    for(int i=1;i<=n;i++)if(num[i]==1)Dicnic::add(0,i,1);
    for(int i=1;i<=n;i++)if(num[i]==cnt)Dicnic::add(i+n,n*2+1,1);
    Dicnic::dicnic(0,n*2+1);
    printf("%lld\n",Dicnic::ans_flow);
    if(n>1){
        Dicnic::add(0,1,inf);
        Dicnic::add(1,n+1,inf);
    }
    if(num[n]==cnt){
        Dicnic::add(n,n+n,inf);
        Dicnic::add(n+n,n*2+1,inf);
    }
    Dicnic::dicnic(0,n*2+1);
    printf("%lld\n",Dicnic::ans_flow);

航空路线问题(最大费用最大流)

问题:n割城市m条单向航线,问从城市1到n再回来的经过城市最多的数量和方案

解法:拆点,其中起点终点流为2,按原图重新建图后跑最大费用流

int find(int now)
{
	for(int i=zkw::head[now];~i;i=zkw::mp[i].next)
        if(zkw::mp[i^1].flow&&zkw::mp[i].to<=n){
            zkw::mp[i].flow++;
            zkw::mp[i^1].flow--;
            return zkw::mp[i].to;
        }
	assert(0);
}

int main()
{	int m;
	scanf("%d%d",&n,&m);
	zkw::init(n+n);
	for(int i=1;i<=n;i++){
		scanf(" %s",s[i]);
		num[get(s[i])]=i;
	}
	for(int i=1;i<=m;i++){
		scanf(" %s %s",s1,s2);
		int x=num[get(s1)],y=num[get(s2)];
		zkw::add(x+n,y,inf,0);
	}
	for(int i=2;i<n;i++)zkw::add(i,i+n,1,-1);
	zkw::add(1,1+n,2,-1);
	zkw::add(n,n+n,2,-1);
	zkw::zkw(1,n+n);
	if(zkw::ans_flow<2){puts("No Solution!");return 0;}
	printf("%lld\n",-zkw::ans_cost-2);
	for(int i=find(1+n);i!=n;i=find(i+n)){
		ans[0].push_back(i);
	}
	for(int i=find(1+n);i!=n;i=find(i+n))
		ans[1].push_back(i);
	reverse(ans[1].begin(),ans[1].end());
	printf("%s\n",s[1]);
	for(auto i:ans[0])printf("%s\n",s[i]);
	printf("%s\n",s[n]);
	for(auto i:ans[1])printf("%s\n",s[i]);
	printf("%s\n",s[1]);
	return 0;
}

方格取数问题(黑白染色,最小割)

问题:给定 n ∗ m n*m nm矩阵,相邻的不能同时取,问取出来的数和最大值

解法:黑白染色(分奇偶拆点),利用最小割把不取的集合割掉,剩下的就是不相邻的必取的点

	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++){
			scanf("%d",&a[i][j]);
			num[i][j]=++cnt;
			sum+=a[i][j];
		}
	Dicnic::init(cnt+1);
	for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)if((i+j)%2==1)
        Dicnic::add(0,num[i][j],a[i][j]);
	for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)if((i+j)%2==0)
        Dicnic::add(num[i][j],cnt+1,a[i][j]);
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)if((i+j)%2)
			for(int k=0;k<4;k++){
				int x=i+Move[k][0],y=j+Move[k][1];
				if(x&&y&&x<=n&&y<=m)
					Dicnic::add(num[i][j],num[x][y],inf);
			}
	Dicnic::dicnic(0,cnt+1);
	printf("%lld\n",sum-Dicnic::ans_flow);

圆桌问题(简单最大流)

问题:n类人,m张桌子,同类不能坐在同一张桌子,问坐的方案

	scanf("%d%d",&n,&m);
	Dicnic::init(n+m+1);
	for(int i=1;i<=n;i++){scanf("%d",&x);Dicnic::add(0,i,x);sum+=x;}
	for(int i=1;i<=m;i++){scanf("%d",&x);Dicnic::add(i+n,n+m+1,x);}
	for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)Dicnic::add(i,j+n,1);
	Dicnic::dicnic(0,n+m+1);
	if(Dicnic::ans_flow<sum){puts("0");return 0;}
	puts("1");
	for(int i=1;i<=n;i++){
		for(int j=Dicnic::head[i];~j;j=Dicnic::mp[j].next)
			if(Dicnic::mp[j^1].flow)printf("%d ",Dicnic::mp[j].to-n);
		puts("");
	}

最长k可重区间集问题

问题: n n n个开区间,选取其中若干个开区间,使得坐标的每个位置被选去的区间中不超过 k k k个区间覆盖,求最大的 ∑ l e n \sum len len

解法:转化成网络流问题,1的流量一个区间的选择,同一个坐标只能被选择一次,因此,可以转换为每个区间 l i → r i l_i \to r_i liri的连边

	scanf("%d%d",&n,&k);
	for(int i=1;i<=n;i++){
		scanf("%d%d",&l[i],&r[i]);
		len[i]=r[i]-l[i];
		v.push_back(l[i]);
		v.push_back(r[i]);
	}
	sort(v.begin(),v.end());
	v.erase(unique(v.begin(),v.end()),v.end());
	for(int i=1;i<=n;i++){
		l[i]=lower_bound(v.begin(),v.end(),l[i])-v.begin()+1;
		r[i]=lower_bound(v.begin(),v.end(),r[i])-v.begin()+1;
	}
	int m=v.size();
	zkw::init(m+1);
	zkw::add(0,1,k,0);
	for(int i=1;i<=m;i++)zkw::add(i,i+1,inf,0);
	for(int i=1;i<=n;i++)zkw::add(l[i],r[i],1,-len[i]);
	zkw::zkw(0,m+1);
	printf("%lld\n",-zkw::ans_cost);

最长k可重线段集问题

问题:二维平面 x O y xOy xOy n n n条线段,选取其中若干条线段,使得与直线 x = p x=p x=p相交的线段不超过 k k k条,求最大的 ∑ l e n \sum len len

解法:与上题类似,只不过由于存在 x 1 = = x 2 x1==x2 x1==x2的情况,需要对 x x x坐标轴进行拆点,再连边

	scanf("%d%d",&n,&k);
	for(int i=1;i<=n;i++){
		scanf("%lld%lld%lld%lld",&x1,&y1,&x2,&y2);
		len[i]=sqrt(sqr(x1-x2)+sqr(y1-y2));
		x1<<=1;x2<<=1;
		if(x1==x2)x2++;
		else x1++;
		l[i]=x1;r[i]=x2;
		v.push_back(l[i]);
		v.push_back(r[i]);
	}
	sort(v.begin(),v.end());
	v.erase(unique(v.begin(),v.end()),v.end());
	for(int i=1;i<=n;i++){
		l[i]=lower_bound(v.begin(),v.end(),l[i])-v.begin()+1;
		r[i]=lower_bound(v.begin(),v.end(),r[i])-v.begin()+1;
	}
	int m=v.size();
	zkw::init(m+1);
	zkw::add(0,1,k,0);
	for(int i=1;i<=m;i++)zkw::add(i,i+1,inf,0);
	for(int i=1;i<=n;i++)zkw::add(l[i],r[i],1,-len[i]);
	zkw::zkw(0,m+1);
	printf("%lld\n",-zkw::ans_cost);

汽车加油行驶问题(分层图)

问题:给定一个 n × n n \times n n×n的网格,一辆车要从左上角走到右下角,汽车装满油只能走 k k k条边,汽车经过一条边时,若 X X X坐标或 Y Y Y坐标减小,应付 B B B费用;加油站必须加油,费用 A A A;无加油站可花费 C + A C+A C+A加油

解法:分层图,按油量分层

	scanf("%d%d%d%d%d",&n,&K,&A,&B,&C);K++;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++){
			scanf("%d",&mp[i][j]);
			num[i][j]=(cnt++)*K;
		}
	zkw::init(cnt*K+1);
	for(int k=1;k<=K;k++){
		for(int i=1;i<=n;i++)
			for(int j=1;j<=n;j++)
				if(!mp[i][j]||k==1){
					if(k<K){
						if(i>1)zkw::add(num[i][j]+k,num[i-1][j]+k+1,inf,B);
						if(j>1)zkw::add(num[i][j]+k,num[i][j-1]+k+1,inf,B);
						if(i<n)zkw::add(num[i][j]+k,num[i+1][j]+k+1,inf,0);
						if(j<n)zkw::add(num[i][j]+k,num[i][j+1]+k+1,inf,0);
					}
					if(k>1)zkw::add(num[i][j]+k,num[i][j]+1,inf,C+A);
				}else{
					zkw::add(num[i][j]+k,num[i][j]+1,inf,A);
				}
		zkw::add(num[n][n]+k,cnt*K+1,inf,0);
	}
	zkw::add(0,1,1,0);
	zkw::zkw(0,cnt*K+1);
	printf("%lld\n",zkw::ans_cost);

LGV引理

Lindström–Gessel–Viennot lemma,即 LGV 引理,可以用来处理有向无环图上不相交路径计数等问题。

在一个有向无环图 G G G中,出发点 A = { a 1 , A − 2 , ⋯   , a n } A=\{a_1,A-2,\cdots,a_n\} A={a1,A2,,an},目标点 B = { b 1 , b 2 , ⋯   , b n } B=\{b_1,b_2,\cdots,b_n\} B={b1,b2,,bn},同时给有向边 e e e分派一个权值 ω e \omega_e ωe a a a b b b的一条路径记作 P : a → b P:a \to b P:ab

e ( u , v ) = ∑ P : a → b ∏ e ∈ P ω e e(u,v)=\sum_{P:a \to b}\prod_{e \in P}\omega_e e(u,v)=P:abePωe(即每一条路径的边权乘积之和)
M = [ e ( A 1 , B 1 ) e ( A 1 , B 2 ) ⋯ e ( A 1 , B n ) e ( A 2 , B 1 ) e ( A 2 , B 2 ) ⋯ e ( A 2 , B n ) ⋮ ⋮ ⋱ ⋮ e ( A n , B 1 ) e ( A n , B 2 ) ⋯ e ( A n , B n ) ] det ⁡ ( M ) = ∑ S : A → B ( − 1 ) N ( σ ( S ) ) ∏ i = 1 n ω ( S i ) M=\left[ \begin{matrix} e(A_1,B_1) & e(A_1,B_2) & \cdots & e(A_1,B_n) \\ e(A_2,B_1) & e(A_2,B_2) & \cdots & e(A_2,B_n) \\ \vdots & \vdots & \ddots & \vdots \\ e(A_n,B_1) & e(A_n,B_2) & \cdots & e(A_n,B_n) \end{matrix} \right] \\ \det(M)=\sum_{S:A \to B}(-1)^{N(\sigma(S))} \prod_{i=1}^{n} \omega(S_i) M=e(A1,B1)e(A2,B1)e(An,B1)e(A1,B2)e(A2,B2)e(An,B2)e(A1,Bn)e(A2,Bn)e(An,Bn)det(M)=S:AB(1)N(σ(S))i=1nω(Si)
det ⁡ ( M ) \det(M) det(M)即为 A → B A \to B AB所有不相交路径在所有排列上的带符号和

当边权全部为1时,求出的就是不相交的路径数量

数据结构

并查集

普通并查集

struct dsu{
    int fa[def];
    void init(int n){for(int i=0;i<=n;i++)fa[i]=i;}
    int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);}
    void merge(int x,int y){fa[find(x)]=find(y);}
};

可撤销并查集

struct ufs{
	int fa[def],size[def];
	stack<pair<int,int> >sta;

	void init(int n){for(int i=0;i<=n;i++)fa[i]=i;}
	int find(int x){return fa[x]==x?x:find(fa[x]);}

	bool merge(int x,int y)
	{
		x=find(x);y=find(y);
		sta.push({x,y});
		if(x==y)return false;
		if(size[x]>size[y])swap(x,y);
		fa[x]=y;
		size[y]+=size[x];
		return true;
	}

	void undo()
	{	auto top=sta.top();sta.pop();
		int x=top.first,y=top.second;
		if(x==y)return;
		fa[x]=x;
		size[y]-=size[x];
	}
};

树状数组

一维

#define lowbit(i) ((i)&-(i))
ll sum[def];
int n;

void add(int x,int y)
{
    for(;x<=n;x+=lowbit(x))
        sum[x]+=y;
}

ll query(int x)
{	ll ans=0;
    for(;x;x-=lowbit(x))
        ans+=x;
 	return ans;
}

二维

#define lowbit(i) ((i)&-(i))
ll sum[def][def];
int n,m;

void add(int x,int y,ll z)
{
	for(int i=x;i<=n;i+=lowbit(i))
		for(int j=y;j<=m;j+=lowbit(j))
			sum[i][j]+=z;
}

ll query(long x,long y)
{
	ll ans=0;
	for(int i=x;i;i-=lowbit(i))
		for(int j=y;j;j-=lowbit(j))
			ans+=sum[i][j];
	return ans;
}

ll get_ans(int x1,int y1,int x2,int y2)
{
	return query(x2,y2)-query(x1-1,y2)-query(x2,y1-1)+query(x1-1,y1-1);
}

线段树

区间和线段树

struct segtree{
	#define lc (now<<1)
	#define rc (now<<1|1)

	ll lazy[def<<2],len[def<<2];
	ll val[def<<2];
	int l=0,r=0;

	void init(int l,int r,ll *a=NULL)
	{
		this->l=l;
		this->r=r;
		build(l,r,a);
	}

	void pushdown(int now)
	{
		if(lazy[now]){
			if(lc<(def<<2))lazy[lc]+=lazy[now];
			if(rc<(def<<2))lazy[rc]+=lazy[now];
			val[now]+=len[now]*lazy[now];
			lazy[now]=0;
		}
	}

	void pushup(int now)
	{	int mid=(l+r)/2;
		pushdown(lc);pushdown(rc);
		val[now]=val[lc]+val[rc];
	}

	void build(int l,int r,ll *a,int now=1)
	{
		len[now]=r-l+1;
		lazy[now]=0;
		if(l==r){
			if(a!=NULL)
				val[now]=a[l];
			else
				val[now]=0;
		}
		else{
			int mid=(l+r)/2;
			build(l,mid,a,lc);
			build(mid+1,r,a,rc);
			pushup(now);
		}
	}

	void upd(int x,ll z){upd(l,r,x,x,z);}//单点修改
	void upd(int x,int y,ll z){upd(l,r,x,y,z);}//区间修改
	void upd(int l,int r,int x,int y,ll z,int now=1)//add or change
	{
		pushdown(now);
		if(x<=l&&r<=y)
			lazy[now]=z;
		else{
			int mid=(l+r)/2;
			if(x<=mid)
				upd(l,mid,x,y,z,lc);
			if(mid<y)
				upd(mid+1,r,x,y,z,rc);
			pushup(now);
		}
	}

	ll query(int x,int y){return query(l,r,x,y);}
	ll query(int l,int r,int x,int y,int now=1)
	{
		pushdown(now);
		if(x<=l&&r<=y)
			return val[now];
		else{
			int mid=(l+r)/2;
			ll val=0;
			if(x<=mid)
				val+=query(l,mid,x,y,lc);
			if(mid<y)
				val+=query(mid+1,r,x,y,rc);
			return val;
		}
	}
};

最值线段树

struct segtree{
	#define lc (now<<1)
	#define rc (now<<1|1)

	ll lazy[def<<2];
	ll maxx[def<<2],minn[def<<2];
	int l=0,r=0;

	void init(int l,int r,ll *a=NULL)
	{
		this->l=l;
		this->r=r;
		build(l,r,a);
	}

	void pushdown(int now)
	{
		if(lazy[now]){
			if(lc<(def<<2))lazy[lc]+=lazy[now];
			if(rc<(def<<2))lazy[rc]+=lazy[now];
			maxx[now]+=lazy[now];
			minn[now]+=lazy[now];
			lazy[now]=0;
		}
	}

	void pushup(int now)
	{	int mid=(l+r)/2;
		pushdown(lc);pushdown(rc);
		maxx[now]=max(maxx[lc],maxx[rc]);
		minn[now]=min(minn[lc],minn[rc]);
	}

	void build(int l,int r,ll *a,int now=1)
	{
		lazy[now]=0;
		if(l==r){
			if(a!=NULL)
				maxx[now]=minn[now]=a[l];
			else
				maxx[now]=minn[now]=0;
		}
		else{
			int mid=(l+r)/2;
			build(l,mid,a,lc);
			build(mid+1,r,a,rc);
			pushup(now);
		}
	}

	void upd(int x,ll z){upd(l,r,x,x,z);}//单点修改
	void upd(int x,int y,ll z){upd(l,r,x,y,z);}//区间修改
	void upd(int l,int r,int x,int y,ll z,int now=1)//add or change
	{
		pushdown(now);
		if(x<=l&&r<=y)
			lazy[now]+=z;
		else{
			int mid=(l+r)/2;
			if(x<=mid)
				upd(l,mid,x,y,z,lc);
			if(mid<y)
				upd(mid+1,r,x,y,z,rc);
			pushup(now);
		}
	}

	ll qmax(int x,int y){return qmax(l,r,x,y);}
	ll qmax(int l,int r,int x,int y,int now=1)
	{
		pushdown(now);
		if(x<=l&&r<=y)
			return maxx[now];
		else{
			int mid=(l+r)/2;
			ll ans=INT64_MIN;
			if(x<=mid)
				ans=max(ans,qmax(l,mid,x,y,lc));
			if(mid<y)
				ans=max(ans,qmax(mid+1,r,x,y,rc));
			return ans;
		}
	}

	ll qmin(int x,int y){return qmin(l,r,x,y);}
	ll qmin(int l,int r,int x,int y,int now=1)
	{
		pushdown(now);
		if(x<=l&&r<=y)
			return minn[now];
		else{
			int mid=(l+r)/2;
			ll ans=INT64_MAX;
			if(x<=mid)
				ans=min(ans,qmin(l,mid,x,y,lc));
			if(mid<y)
				ans=min(ans,qmin(mid+1,r,x,y,rc));
			return ans;
		}
	}
};

线段树区间合并

#define lc (now<<1)
#define rc (now<<1)+1

struct node{
	long l,r,mid,len;
}tree[def*4];
long a[def];

void change(long l,long r,long x,long now=1)
{
	if(l==r)
		tree[now]={1,1,1,1};
	else{
		long mid=(l+r)/2,len;
		if(x<=mid)
			change(l,mid,x,lc);
		else change(mid+1,r,x,rc);
		if(a[mid]>=a[mid+1])
			tree[now]={
				tree[lc].l,
				tree[rc].r,
				max(tree[lc].mid,tree[rc].mid),
				r-l+1
			};
		else
			tree[now]={
				(tree[lc].l==tree[lc].len)?(tree[lc].len+tree[rc].l):tree[lc].l,
				(tree[rc].r==tree[rc].len)?(tree[rc].len+tree[lc].r):tree[rc].r,
				max(max(tree[lc].mid,tree[rc].mid),tree[lc].r+tree[rc].l),
				r-l+1
			};
	}
}

node query(long l,long r,long x,long y,long now=1)
{
	if(x<=l&&r<=y)
		return tree[now];
	else{
		long mid=(l+r)/2,len;
		node ansl={0,0,0,0},ansr={0,0,0,0},ans;
		if(x<=mid)
			ansl=query(l,mid,x,y,lc);
		if(mid<y)
			ansr=query(mid+1,r,x,y,rc);
		if(!ansl.l)return ansr;
		if(!ansr.r)return ansl;
		if(a[mid]>=a[mid+1])
			ans={
				ansl.l,
				ansr.r,
				max(ansl.mid,ansr.mid),
				ansl.len+ansr.len
			};
		else
			ans={
				(ansl.l==ansl.len)?(ansl.len+ansr.l):ansl.l,
				(ansr.r==ansr.len)?(ansr.len+ansl.r):ansr.r,
				max(max(ansl.mid,ansr.mid),ansl.r+ansr.l),
				ansl.len+ansr.len
			};
		return ans;
	}
}

可持久化数据结构

可持久化数组

struct Persistable_Segment_Tree{
	struct node{
		int val,lc,rc;
	}tr[def<<4];
	int root[def],cnt,L,R;

	void init(){cnt=L=R=0;}

	int clone(int now)
	{
		tr[++cnt]=tr[now];
		return cnt;
	}

	int build(int l,int r,int *a=NULL){
		L=l;R=r;
		return _build(l,r,a);
	}
	int _build(int l,int r,int *a){
		int now=++cnt;
		if(l==r){
			if(a==NULL)
				tr[now].val=0;
			else
				tr[now].val=a[l];
		}else{
			int mid=(l+r)>>1;
			tr[now].lc=_build(l,mid,a);
			tr[now].rc=_build(mid+1,r,a);
		}
		return now;
	}

	//更新某个版本的一个点,并返回新版本的根
	int upd(int x,int z,int now){return upd(L,R,x,z,now);}
	int upd(int l,int r,int x,int z,int now)
	{
		now=clone(now);
		if(l==r){
			tr[now].val=z;
		}else{
			int mid=(l+r)>>1;
			if(x<=mid)
				tr[now].lc=upd(l,mid,x,z,tr[now].lc);
			else
				tr[now].rc=upd(mid+1,r,x,z,tr[now].rc);
		}
		return now;
	}

	//查询某个版本的一个点
	int query(int x,int now){return query(L,R,x,now);}
	int query(int l,int r,int x,int now)
	{
		if(l==r){
			return tr[now].val;
		}else{
			int mid=(l+r)>>1;
			if(x<=mid)
				return query(l,mid,x,tr[now].lc);
			else
				return query(mid+1,r,x,tr[now].rc);
		}
	}
};

可持久化线段树

struct Persistable_Segment_Tree{
	struct node{
		ll val;
		int lc,rc;
	}tr[def<<5];
	int root[def],cnt,L,R;

	inline void init(){cnt=L=R=0;}

	inline int clone(int now)
	{
		tr[++cnt]=tr[now];
		return cnt;
	}

	inline void pushup(int now)
	{
		int lc=tr[now].lc,rc=tr[now].rc;
		tr[now].val=tr[lc].val+tr[rc].val;
	}

	inline int build(int l,int r,ll *a=NULL)
	{
		L=l;R=r;
		return _build(l,r,a);
	}

	inline int _build(int l,int r,ll *a)
	{
		int now=++cnt;
		if(l==r){
			if(a==NULL)
				tr[now].val=0;
			else
				tr[now].val=a[l];
		}else{
			int mid=(l+r)>>1;
			tr[now].lc=_build(l,mid,a);
			tr[now].rc=_build(mid+1,r,a);
			pushup(now);
		}
		return now;
	}

	inline int upd(int x,ll z,int now){return upd(L,R,x,z,now);}
	inline int upd(int l,int r,int x,ll z,int now)
	{
		now=clone(now);
		if(l==r){
			tr[now].val+=z;
		}else{
			int mid=(l+r)>>1;
			if(x<=mid)
				tr[now].lc=upd(l,mid,x,z,tr[now].lc);
			else
				tr[now].rc=upd(mid+1,r,x,z,tr[now].rc);
			pushup(now);
		}
		return now;
	}

	//查询版本[l,r]之间,[x,y]的和
	inline ll query(int l,int r,int x,int y){return query(L,R,x,y,root[r],root[l-1]);}
	inline ll query(int l,int r,int x,int y,int now1,int now2)
	{
		if(x<=l&&r<=y){
			return tr[now1].val-tr[now2].val;
		}
		else{
			int mid=(l+r)>>1;
			ll ans=0;
			if(x<=mid)
				ans+=query(l,mid,x,y,tr[now1].lc,tr[now2].lc);
			if(mid<y)
				ans+=query(mid+1,r,x,y,tr[now1].rc,tr[now2].rc);
			return ans;
		}
	}

	//查询版本[l,r]之间,第k小的数
	inline int kth(int x,int y,int k){return kth(L,R,k,root[y],root[x-1]);}
	inline int kth(int l,int r,int k,int now1,int now2)
	{
		if(l==r)return l;
		else{
			int mid=(l+r)>>1;
			int lc1=tr[now1].lc,lc2=tr[now2].lc;
			int rc1=tr[now1].rc,rc2=tr[now2].rc;
			if(k<=tr[lc1].val-tr[lc2].val)
				return kth(l,mid,k,lc1,lc2);
			else
				return kth(mid+1,r,k-(tr[lc1].val-tr[lc2].val),rc1,rc2);
		}
	}
}tr;

树链剖分

vector<int>mp[def];
int size[def],fa[def],dep[def],son[def],id[def],top[def];
int cnt;

void dfs1(int now)
{
	size[now]=1;
	son[now]=0;
	for(auto i:mp[now])
		if(i!=fa[now]){
			fa[i]=now;
			dep[i]=dep[now]+1;
			dfs1(i);
			size[now]+=size[i];
			if(size[i]>size[son[now]])
				son[now]=i;
		}
}

void dfs2(int now,int t)
{
	id[now]=++cnt;
	top[now]=t;
	if(!son[now])return;
	dfs2(son[now],t);
	for(auto i:mp[now])
		if(i!=fa[now]&&i!=son[now])
			dfs2(i,i);
}

void pou(int root)
{
	cnt=0;
	fa[root]=0;
	dep[root]=1;
	dfs1(root);
	dfs2(root,root);
}

int lca(int l,int r)
{
	while(top[l]!=top[r]){
		if(dep[top[l]]<dep[top[r]])swap(l,r);
		l=fa[top[l]];
	}
	if(dep[l]>dep[r])swap(l,r);
	return r;
}

void upd(int l,int r,int val)
{
	while(top[l]!=top[r]){
		if(dep[top[l]]<dep[top[r]])swap(l,r);
		//tr.upd(id[top[l]],id[l],val);
		l=fa[top[l]];
	}
	if(dep[l]>dep[r])swap(l,r);
	//tr.upd(id[l],id[r],val);
}

ll getans(int l,int r)
{	ll ans=0;
	while(top[l]!=top[r]){
		if(dep[top[l]]<dep[top[r]])swap(l,r);
		//ans+=tr.getsum(id[top[l]],id[l]);
		l=fa[top[l]];
	}
	if(dep[l]>dep[r])swap(l,r);
	//ans+=tr.getsum(id[l],id[r]);
	return ans;
}

ST表

#define bit 31

int n,maxx[def][bit];

void init(int n,int *a)
{
    for(int i=1;i<=n;i++)
        maxx[i][0]=a[i];
	for(int j=1;(1<<j)<=n;j++)
		for(int i=1;i+(1<<(j-1))<=n;i++)
			maxx[i][j]=max(maxx[i][j-1],maxx[i+(1<<(j-1))][j-1]);
}

int query(int x,int y)
{	int len=log2(y-x+1);
    return max(maxx[x][len],maxx[y-(1<<len)+1][len]);
}

平衡树

普通平衡树(基于树状数组)

基于权值树状数组,数的值域 [ − 1 0 7 , 1 0 7 ] [-10^7,10^7] [107,107]

struct Balanced_Tree_with_BIT{
	const int N=1<<25,p=1e7+10;
	int sum[N+1];

	void add(int x,int y)
	{
		for(x+=p;x<=N;x+=lowbit(x))sum[x]+=y;
	}

	int query(int x)
	{	int ans=0;
		for(x+=p;x;x-=lowbit(x))ans+=sum[x];
		return ans;
	}

	int kth(int k)
	{	int l=1,r=N;
		while(l^r){
			int mid=(l+r)>>1;
			if(sum[mid]<k)
				k-=sum[mid],l=mid+1;
			else
				r=mid;
		}
		return l-p;
	}

	int rnk(int x){return query(x-1)+1;}
	int pre(int x){return kth(query(x-1));}
	int nxt(int x){return kth(query(x)+1);}
};

Splay(区间翻转)

struct Splay{
	int rev[def],fa[def],size[def],val[def],ch[def][2];
	int root,cnt;

	void init()
	{
		root=cnt=0;
	}

	void build(int l,int r,int &now)
	{	int mid=(l+r)/2;
		if(r<l)return;
		if(!now)now=++cnt;
		rev[now]=0;
		val[now]=mid;
		ch[now][0]=ch[now][1]=0;
		build(l,mid-1,ch[now][0]);
		build(mid+1,r,ch[now][1]);
		fa[ch[now][0]]=fa[ch[now][1]]=now;
		pushup(now);
	}

	void pushup(int x)
	{
		size[x]=size[ch[x][0]]+size[ch[x][1]]+1;
	}

	void pushdown(int x)
	{
		if(x&&rev[x]){
			rev[ch[x][0]]^=1;
			rev[ch[x][1]]^=1;
			rev[x]=0;
			swap(ch[x][0],ch[x][1]);
		}
	}

	void rotate(int x)
	{	int y=fa[x];//x的fa
		int z=fa[y];//x的fa的fa
		//pushdown(y);pushdown(x);
		int k=ch[y][1]==x;
		ch[z][ch[z][1]==y]=x;
		fa[x]=z;
		ch[y][k]=ch[x][!k];
		fa[ch[x][!k]]=y;
		ch[x][!k]=y;
		fa[y]=x;
		pushup(y);pushup(x);
	}

	void splay(int x,int goal)//将x旋转为goal的儿子
	{
		while(fa[x]!=goal){
			int y=fa[x],z=fa[y];
			if(z!=goal)
				(ch[z][0]==y)^(ch[y][0]==x)?rotate(x):rotate(y);
			rotate(x);
		}
		if(!goal)root=x;
	}

	int find(int x)
	{	int u=root;
		if(!u)return 0;
		while(1){
			pushdown(u);
			while(x<=size[ch[u][0]]){
				u=ch[u][0];
				pushdown(u);
			}
			if(x==size[ch[u][0]]+1)return u;
			x-=size[ch[u][0]]+1;
			u=ch[u][1];
		}
		return u;
	}

	void reverse(int x,int y)
	{	int l=find(x-1),r=find(y+1);
		splay(l,0);
		splay(r,l);
		int pos=ch[ch[root][1]][0];
		//找到区间(x-1,y+1)即[x,y]对应的根
		rev[pos]^=1;
		//pushdown(pos);
	}

	void print(int now,int n)
	{
		pushdown(now);
		if(!now)return;
		print(ch[now][0],n);
		if(val[now]>1&&val[now]<n+2)printf("%d ",val[now]-1);
		print(ch[now][1],n);
	}
};

普通平衡树(基于Splay)

struct Splay{
	int fa[def],size[def],num[def],ch[def][2];
	int root,cnt,minpos,maxpos;
	ll val[def];

	Splay(){init();}

	void init(){
		root=cnt=0;
		ins(INT64_MIN);
		minpos=cnt;
		ins(INT64_MAX);
		maxpos=cnt;
	}

	void pushup(int x)
	{
		if(x)size[x]=size[ch[x][0]]+size[ch[x][1]]+num[x];
	}

	void pushdown(int x){}

	void connect(int x,int y,int z)//把x接到y下面
	{
		ch[y][z]=x;
		fa[x]=y;
	}

	void rotate(int x)
	{	int y=fa[x];//x的fa
		int z=fa[y];//x的fa的fa
		pushdown(y);pushdown(x);
		int k=ch[y][1]==x;
		connect(x,z,ch[z][1]==y);
		connect(ch[x][!k],y,k);
		connect(y,x,!k);
		pushup(y);pushup(x);
	}

	void splay(int x,int goal=0)//将x旋转为goal的儿子
	{
		if(!x)return;
		while(fa[x]!=goal){
			int y=fa[x],z=fa[y];
			if(z!=goal)
				(ch[z][0]==y)^(ch[y][0]==x)?rotate(x):rotate(y);
			rotate(x);
		}
		if(!goal)root=x;
	}

	void print(int now,int n,ll l=INT64_MIN,ll r=INT64_MAX)//输出[l,r]内的数
	{
		pushdown(now);
		if(!now)return;
		print(ch[now][0],n,l,r);
		if(val[now]>=l&&val[now]<r)
			for(int i=1;i<=num[now])
				printf("%lld ",val[now]);
		print(ch[now][1],n,l,r);
	}

	void create(ll v,int father)
	{
		cnt++;
		val[cnt]=v;
		fa[cnt]=father;
		num[cnt]=1;
		pushup(cnt);
	}

	void destroy(int now)
	{
		size[now]=num[now]=val[now]=fa[now]=ch[now][0]=ch[now][1]=0;
	}

	int find(ll v)//找到v,并转到根
	{	int now=root;
		while(now){
			int nt=v<val[now]?0:1;
			if(val[now]==v){
				splay(now);
				return now;
			}
			if(!ch[now][nt])return 0;
			now=ch[now][nt];
		}
		return 0;
	}

	int pre(ll v)
	{	int now=root;
		int ans=minpos;
		while(now){
			if(val[now]<v&&val[now]>val[ans])ans=now;
			now=ch[now][v<=val[now]?0:1];
		}
		return ans;
	}

	int nxt(ll v)
	{	int now=root;
		int ans=maxpos;
		while(now){
			if(val[now]>v&&val[now]<val[ans])ans=now;
			now=ch[now][v<val[now]?0:1];
		}
		return ans;
	}

	int add(ll v)
	{
		if(!root){
			create(v,0);
			return root=cnt;
		}
		else{
			int now=root;
			while(now){
				int nt=v<val[now]?0:1;
				if(v==val[now]){
					num[now]++;
					pushup(now);
					return now;
				}
				if(!ch[now][nt]){
					create(v,now);
					ch[now][nt]=cnt;
					return cnt;
				}
				now=ch[now][nt];
			}
			return 0;
		}
	}
	void ins(ll v){splay(add(v));}

	void del(ll v)
	{	int now=find(v);//now已经是root了
		if(!now)return;
		if(num[now]>1){
			num[now]--;
			pushup(now);
			return;
		}
		if(!ch[now][0])
			root=ch[now][1];
		else{
			splay(pre(v),now);
			int lc=ch[now][0],rc=ch[now][1];
			connect(rc,lc,1);
			root=lc;
		}
		fa[root]=0;
		destroy(now);
	}

	int rnk(ll v)//取得v的排名(比v小的数的个数+1)
	{
		splay(pre(v));
		return size[ch[root][0]]+num[root];
	}

	ll kth(int rank)
	{	int now=root;
		rank++;
		while(now){
			rank-=size[ch[now][0]];
			if(rank>=1&&rank<=num[now])break;
			if(rank>num[now]){
				rank-=num[now];
				now=ch[now][1];
			}
			else{
				rank+=size[ch[now][0]];
				now=ch[now][0];
			}
		}
		if(now)splay(now);
		return val[now];
	}

	ll lower(ll v){int now=pre(v);splay(now);return val[now];}
	ll upper(ll v){int now=nxt(v);splay(now);return val[now];}
}tr;

动态树

Link Cut Tree O ( n log ⁡ n ) O(n\log n) O(nlogn)

LCT维护森林的连通性,也可维护树链上的值

struct LCT{
	int rev[def],fa[def],val[def],son[def][2];
	int sta[def];

	void init(int n)
	{
		for(int i=0;i<=n;i++){
			rev[i]=fa[i]=son[i][0]=son[i][1]=0;
			//val[i]=inf;
		}
	}

	bool isroot(int x){return son[fa[x]][0]!=x&&son[fa[x]][1]!=x;}
	int getson(int x){return son[fa[x]][1]==x;}

	void pushup(int x)
	{
        
	}

	void pushdown(int x)
	{
		if(!rev[x])return;
		rev[son[x][0]]^=1;
		rev[son[x][1]]^=1;
		rev[x]=0;
		swap(son[x][0],son[x][1]);
	}


	void rotate(int x)
	{
		int f=fa[x],g=fa[f],l=getson(x),r=l^1;
		if(!isroot(f))son[g][getson(f)]=x;
		fa[x]=g;
		fa[f]=x;
		fa[son[x][r]]=f;
		son[f][l]=son[x][r];
		son[x][r]=f;
		pushup(f);
		pushup(x);
	}

	void splay(int x)//将x旋转为根
	{	int cnt=0;
		sta[++cnt]=x;
		for(int i=x;!isroot(i);i=fa[i])sta[++cnt]=fa[i];
		for(;cnt;cnt--)pushdown(sta[cnt]);
		for(;!isroot(x);rotate(x))if(!isroot(fa[x])){
			rotate(getson(fa[x])^getson(x)?x:fa[x]);
		}
	}

	void access(int x)
	{
		for(int i=0;x;i=x,x=fa[x]){
			splay(x);
			son[x][1]=i;
			pushup(x);
		}
	}

	int find(int x){access(x);splay(x);while(son[x][0])x=son[x][0];return x;}
	void makeroot(int x){access(x);splay(x);rev[x]^=1;}
	void split(int x,int y){makeroot(x);access(y);splay(y);}
	void link(int x,int y){makeroot(x);fa[x]=y;}
	void cut(int x,int y){split(x,y);son[y][0]=fa[x]=0;pushup(y);}
};

杂项

莫队

普通莫队 n n n\sqrt n nn

#include<bits/stdc++.h>
typedef unsigned long long ull;
typedef long long ll;
template<class T1,class T2,class T3>T1 ksm(T1 a,T2 b,T3 mod){T1 ans=1;a%=mod;while(b){if(b&1)ans=(ans*a)%mod;a=(a*a)%mod;b>>=1;}return ans;}
template<class T>T gcd(T a,T b){T r;while(b>0){r=a%b;a=b;b=r;}return a;}
#define inf INT_MAX
#define lowbit(i) ((i)&-(i))
#define Max(x,y) x=max(x,y)
#define Min(x,y) x=min(x,y)
#define Out(t) puts((t)?"YES":"NO")
const double pi=acos(-1.);
const double eps=1e-10;
const int def=100010;
const int mod=1000000007;
using namespace std;

struct node{
	int x,y,id;
}b[def];
int a[def],belong[def],num[def],ans[def];

int main()
{	int _=1,__=1,n,m,cnt,l,r,now,x,y;
	for(((0)?scanf("%d",&_):EOF);_;_--,__++){
		scanf("%d",&n);
		int q=sqrt(n);
		for(int i=0;i*q<=n;i++)
			for(int j=0;j<q&&i*q+j<=n;j++)
				belong[i*q+j]=i;
		for(int i=1;i<=n;i++)
			scanf("%d",&a[i]);
		scanf("%d",&m);
		for(int i=1;i<=m;i++){
			scanf("%d%d",&b[i].x,&b[i].y);
			b[i].id=i;
		}
		sort(b+1,b+m+1,[&](node a,node b){return (belong[a.x]^belong[b.x])?(belong[a.x]<belong[b.x]):(belong[a.x]&1)?(a.y<b.y):(a.y>b.y);});
		now=0;
		l=r=0;
		for(int i=1;i<=m;i++){
			x=b[i].x;y=b[i].y;
			while(l<x)now-=!--num[a[l++]];//del(l++);
			while(l>x)now+=!num[a[--l]]++;//add(--l);
			while(r<y)now+=!num[a[++r]]++;//add(++r);
			while(r>y)now-=!--num[a[r--]];//del(r--);
			ans[b[i].id]=now;
		}
		for(int i=1;i<=m;i++)
			printf("%d\n",ans[i]);
	}
	return 0;
}

带修莫队 ( n + m ) 5 3 (n+m)^\frac53 (n+m)35

#include<bits/stdc++.h>
typedef unsigned long long ull;
typedef long long ll;
template<class T1,class T2,class T3>T1 ksm(T1 a,T2 b,T3 mod){T1 ans=1;a%=mod;while(b){if(b&1)ans=(ans*a)%mod;a=(a*a)%mod;b>>=1;}return ans;}
template<class T>T gcd(T a,T b){T r;while(b>0){r=a%b;a=b;b=r;}return a;}
#define inf INT_MAX
#define lowbit(i) ((i)&-(i))
#define Max(x,y) x=max(x,y)
#define Min(x,y) x=min(x,y)
#define Out(t) puts((t)?"YES":"NO")
const double pi=acos(-1.);
const double eps=1e-10;
const int def=1e6+5;
const int mod=1000000007;
using namespace std;

struct node{int x,y,t,id;}b[def];
struct node1{int pos,pre,nxt;}c[def];
int a[def],num[def],ans[def];

// bool cmp(node &a,node &b)
// {
// 	if((a.x/q)^(b.x/q))return (a.x/q)<(b.x/q);
// 	if((a.y/q)^(b.y/q))return (a.y/q)<(b.y/q);
// 	return a.t<b.t;
// }

int main()
{	int _=1,__=1,n,m,x,y,t;
	char op;
	for(((0)?scanf("%d",&_):EOF);_;_--,__++){
		scanf("%d%d",&n,&m);
		for(int i=1;i<=n;i++)scanf("%d",&a[i]);
		int cntb=0,cntc=0;
		for(int i=1;i<=m;++i){
			scanf(" %c %d%d",&op,&x,&y);
			if(op=='R'){//修改
				c[++cntc]={x,a[x],y};
				a[x]=y;
			}else{//查询
				cntb++;
				b[cntb]={x,y,cntc,cntb};
			}
		}
		int q=pow(n+m,2./3.);
		sort(b+1,b+cntb+1,[&](node &a,node &b){return ((a.x/q)^(b.x/q))?((a.x/q)<(b.x/q)):(((a.y/q)^(b.y/q))?(a.y/q)<(b.y/q):(a.t<b.t));});
		for(int i=cntc;i>=1;i--)a[c[i].pos]=c[i].pre;
		int l=1,r=0,tnow=0;
		int now=0;
		for(int i=1;i<=cntb;++i){
			x=b[i].x,y=b[i].y,t=b[i].t;
			while(l<x)now-=!--num[a[l++]];//del(l++);
			while(l>x)now+=!num[a[--l]]++;//add(--l);
			while(r<y)now+=!num[a[++r]]++;//add(++r);
			while(r>y)now-=!--num[a[r--]];//del(r--);
			while(tnow<t){
				int pos=c[++tnow].pos;
				if(l<=pos&&pos<=r)now-=!--num[a[pos]];
				a[pos]=c[tnow].nxt;
				if(l<=pos&&pos<=r)now+=!num[a[pos]]++;
			}
			while(tnow>t){
				int pos=c[tnow].pos;
				if(l<=pos&&pos<=r)now-=!--num[a[pos]];
				a[pos]=c[tnow--].pre;
				if(l<=pos&&pos<=r)now+=!num[a[pos]]++;
			}
			ans[b[i].id]=now;
		}
		for(int i=1;i<=cntb;++i)
			printf("%d\n",ans[i]);
	}
	return 0;
}

C++其他知识点

关闭同步

ios::sunc_with_stdio(false);
cin.tie(0);
cout.tie(0);

8/16进制输入输出

scanf/printf:
	%x,%lx,%llx: 16进制小写
	%X,%lX,%llX: 16进制大写
	%o,%lo,%llo:  8进制
cout:
	hex: 16进制
	oct:  8进制

C++输出小数

cout<<fixed<<setprecision(2)<<x;
cout.unsetf(ios::fixed);
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值