文章目录
- 常用板子
- 字符串
- 数学
- 计算几何
- 计算几何模板
- 基本函数与常量
- 二维点类
- 二维线段直线类
- 多边形类
- 凸多边形类
- 三维点类
- 三维直线与平面类
- 平面法向量
- 判断三点一线
- 判断四点共面
- 判断直线平行
- 判断直线垂直
- 判断平面内两点在直线同侧
- 判断平面内两点在直线异侧
- 判断点在线段上(包含端点)
- 判断点在线段上(不含端点)
- 判断线段是否相交(包含端点)
- 判断线段是否相交(不含端点)
- 两条直线的交点(需保证相交)
- 点到直线的距离
- 直线到直线的距离(不平行)
- 两条直线的夹角的cos值
- 点绕向量逆时针旋转(弧度)
- 判断两点在平面同侧
- 判断两点在平面异侧
- 判断直线与平面平行
- 判断两平面平行
- 判断直线与平面垂直
- 判断两平面垂直
- 判断点在三角形内(包含边界)
- 判断点在三角形内(不含边界)
- 判断线段与三角形相交(包含边界)
- 判断线段与三角形相交(不含边界)
- 直线与平面的交点
- 两平面的交线
- 点到面的距离
- 两平面的夹角的cos值
- 直线与平面的夹角的sin值
- 圆类
- 球类
- 其他知识或技巧
- 图论
- 数据结构
- 杂项
- C++其他知识点
常用板子
基本模板
#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 a∗x1∗c/gcd(a,b)+b∗y1∗c/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+k∗b/gcd(a,b),y=y1−k∗a/gcd(a,b),k∈Z}
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,x∈Z∗,y∈Z∗成立的充要条件是 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=nDn−1+(−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, (p≤1e5)
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) dis2→dis3:(x,y)→(x+y,x−y)
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}) dis3→dis2:(x,y)→(2x+y,2x−y)
用处:去掉 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} xnow−h→xnow的范围内更新答案,区间内按照 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(a−b,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+2b−1
变形可得: a = S − b 2 + 1 a=S-\frac b2 +1 a=S−2b+1
皮克定理与欧拉公式( V − E + F = 2 V-E+F=2 V−E+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+b∣a∈A,b∈B}
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 a∨b可以转换为 ¬ a → b ∧ ¬ b → a \lnot a\to b \land \lnot b\to a ¬a→b∧¬b→a,按着建图,如果 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 xi≤xj&&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 n∗m矩阵,相邻的不能同时取,问取出来的数和最大值
解法:黑白染色(分奇偶拆点),利用最小割把不取的集合割掉,剩下的就是不相邻的必取的点
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 li→ri的连边
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,A−2,⋯,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:a→b
设
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:a→b∏e∈Pω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:A→B∑(−1)N(σ(S))i=1∏nω(Si)
det
(
M
)
\det(M)
det(M)即为
A
→
B
A \to B
A→B所有不相交路径在所有排列上的带符号和
当边权全部为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);