数据结构
以下数据结构均采用ll作为值类型,应用时根据需求调整。
typedef long long ll;
const ll INF=1e9;//表示(值)正无穷,且两个正无穷相加不会溢出
const int NPOS=-1;//表示(下标)不存在
离散化
在vector基础上的离散化,使用push_back()向其中插值,init()排序并离散化,ask查询离散化之后的值,at/[]运算符查离散前的值。
struct Ranker:vector<ll>
{
void init()
{
sort(begin(),end()),resize(unique(begin(),end())-begin());
}
int ask(ll x)const
{
return lower_bound(begin(),end(),x)-begin();
}
};
并查集
struct UnionFindSet:vector<int>
{
int siz;//剩余连通块数量
UnionFindSet(int n):siz(n)
{
for(int i=0; i<n; ++i)push_back(i);
}
int fa(int u)//u所在连通块上根节点
{
return at(u)!=u?at(u)=fa(at(u)):u;
}
void merge(int u,int w)//归并w所在连通块到u所在连通块
{
if(w=fa(w),u=fa(u),w!=u)at(w)=u,--siz;
}
};
单调队列
typedef pair<int,ll> pil;
struct MonotoneQueue:deque<pil>
{
void push(pil p,int k)//first插入元素下标,second插入值,插入并维护一个值单调递增的单调队列且队尾队首下标差值小于k
{
while(!empty()&&back().second>=p.second)pop_back();
for(push_back(p); p.first-front().first>=k;)pop_front();
}
};
ST表
预处理, 求静态区间最小值。
/*
//可选优化
#define log2(n) LOG2[n]
struct Log:vector<ll>
{
Log(int N,ll E):vector<ll>(N,-1)
{
for(int i=1; i<N; ++i)at(i)=at(i/E)+1;
}
} LOG2(N,2);
*/
struct SparseTable
{
vector<vector<ll> > f;
SparseTable(const vector<ll> &a):f(log2(a.size())+1,a)
{
for(int k=0; k+1<f.size(); ++k)
for(int i=0; i+(1<<k)<a.size(); ++i)
f[k+1][i]=min(f[k][i],f[k][i+(1<<k)]);
}
ll ask(int l,int r)
{
int k=log2(r-l+1);
return min(f[k][l],f[k][r+1-(1<<k)]);
}
};
树状数组
模板中Base是对应的基础版本,支持单点修改区间查询。
一维
struct Fenwick
{
struct BaseFenwick
{
vector<ll> v;
BaseFenwick(int last):v(last+1,0) {}
void add(int x,ll w)
{
for(; x<v.size(); x+=x&-x)v[x]+=w;
}
ll ask(int x)
{
ll ans=0;
for(; x; x-=x&-x)ans+=v[x];
return ans;
}
};
pair<BaseFenwick,BaseFenwick> p;
Fenwick(int last):p(last,last) {}
void add(int x,ll w)
{
p.first.add(x,w),p.second.add(x,x*w);
}
void add(int l,int r,ll w)
{
add(l,w),add(r+1,-w);
}
ll ask(int x)
{
return (x+1)*p.first.ask(x)-p.second.ask(x);
}
ll ask(int l,int r)
{
return ask(r)-ask(l-1);
}
};
二维
高维的数据结构只要每一维维护低一维的数据即可。其余数据结构亦同理。
struct Fenwick2
{
struct BaseFenwick2
{
vector<Fenwick> v;
BaseFenwick2(int r,int c):v(r+1,c) {}
void add(int x,int b,int t,ll w)
{
for(; x<v.size(); x+=x&-x)v[x].add(b,t,w);
}
ll ask(int x,int b,int t)
{
ll ans=0;
for(; x; x-=x&-x)ans+=v[x].ask(b,t);
return ans;
}
};
pair<BaseFenwick2,BaseFenwick2> p;
Fenwick2(int r,int c):p(BaseFenwick2(r,c),BaseFenwick2(r,c)) {}
void add(int x,int b,int t,ll w)
{
p.first.add(x,b,t,w),p.second.add(x,b,t,x*w);
}
void add(int l,int b,int r,int t,ll w)//(l,b)~(r,t)
{
add(l,b,t,w),add(r+1,b,t,-w);
}
ll ask(int x,int b,int t)
{
return (x+1)*p.first.ask(x,b,t)-p.second.ask(x,b,t);
}
ll ask(int l,int b,int r,int t)
{
return ask(r,b,t)-ask(l-1,b,t);
}
};
线段树
空间优化的线段树,支持区间查询/增加/修改/合并,使用时仅需根据实际情况修改Node
、push_up
、maintain
、ask
。
struct SegmentTree
{
struct Node
{
ll add,set,min,sum;
};
vector<Node> v;
int LAST,L,R;//序列右端点,操作序列左右端点
SegmentTree(int n):LAST(n),v(2*n+1) {}
void build(ll a[],int l,int r)//快速建树,调用build(a,1,n)
{
if(l<r)
{
int m=l+(r-l)/2;
build(a,l,m),build(a,m+1,r);
lv(l,r).set=INF,lv(l,r).add=0;//清除本节点标记
}
else lv(l,r).set=a[l],lv(l,r).add=0;//两个的和为a[l]即可,根据需要自行选择
maintain(l,r);
}
Node &lv(int l,int r)//[l,r]对应的树上节点
{
return v[l+r|l!=r];
}
void push_down(Node &lc,Node &rc,Node &fa)//将fa的set、add标记传到lc、rc
{
if(fa.set!=INF)
{
lc.set=rc.set=fa.set,fa.set=INF;
lc.add=rc.add=0;
}
lc.add+=fa.add;
rc.add+=fa.add;
fa.add=0;
}
void push_up(const Node &lc,const Node &rc,Node &fa)//将区间左右相连的lc、rc归并到fa
{
fa.min=min(lc.min,rc.min);
fa.sum=lc.sum+rc.sum;
}
void maintain(int l,int r)//维护[l,r]
{
if(l<r)
{
int m=l+(r-l)/2;
push_up(lv(l,m),lv(m+1,r),lv(l,r));
}
if(lv(l,r).set!=INF)
lv(l,r).sum=(r-l+1)*(lv(l,r).min=lv(l,r).set);
lv(l,r).min+=lv(l,r).add;
lv(l,r).sum+=lv(l,r).add*(r-l+1);
}
Node ask(int l,int r,ll val=0,bool out=1)//查询[L,R],val为查询路径累加的add标记,out为外部调用标记,下同
{
if(out)return L=l,R=r,ask(1,LAST,val,0);
if(lv(l,r).set!=INF)
v[0].sum=(min(R,r)-max(l,L)+1)*(v[0].min=val+lv(l,r).add+lv(l,r).set);
else if(L<=l&&r<=R)
v[0].min=lv(l,r).min+val,v[0].sum=lv(l,r).sum+val*(r-l+1);
else
{
int m=l+(r-l)/2;
if(R<=m)return ask(l,m,lv(l,r).add+val,0);
if(L>m)return ask(m+1,r,lv(l,r).add+val,0);
push_up(ask(l,m,lv(l,r).add+val,0),ask(m+1,r,lv(l,r).add+val,0),v[0]);
}
return v[0];
}
void add(int l,int r,ll val,bool out=1)//[L,R]加上val
{
if(out)return L=l,R=r,add(1,LAST,val,0);
if(L<=l&&r<=R)lv(l,r).add+=val;
else
{
int m=l+(r-l)/2;
push_down(lv(l,m),lv(m+1,r),lv(l,r));
if(L<=m)add(l,m,val,0);
else maintain(l,m);
if(R>m)add(m+1,r,val,0);
else maintain(m+1,r);
}
maintain(l,r);
}
void set(int l,int r,ll val,bool out=1)//[L,R]设为val
{
if(out)return L=l,R=r,set(1,LAST,val,0);
if(L<=l&&r<=R)lv(l,r).set=val,lv(l,r).add=0;
else
{
int m=l+(r-l)/2;
push_down(lv(l,m),lv(m+1,r),lv(l,r));
if(L<=m)set(l,m,val,0);
else maintain(l,m);
if(R>m)set(m+1,r,val,0);
else maintain(m+1,r);
}
maintain(l,r);
}
};
主席树
求区间第k小,可能需要离散化。
struct HJTree
{
struct Node
{
int ch[2],sum;
} v[MAXN<<5];
int m,siz,tot,T[MAXN];//siz前缀树的个数,tot所有顶点的个数
void build(int a[],int n,int m)//用a[1]~a[n]建树,a[i]的值不超过m
{
for(this->m=m,siz=tot=1; siz<=n; ++siz)
ins(&(T[siz]=T[siz-1]),a[siz]);
}
void ins(int *rt,int pos,int val=1)
{
for(int l=1,r=m;;)
{
v[tot]=v[*rt],v[*rt=tot++].sum+=val;
if(l==r)break;
int m=l+(r-l)/2;
if(pos>m)rt=&v[*rt].ch[1],l=m+1;
else rt=&v[*rt].ch[0],r=m;
}
}
int ask(int x,int y,int k)
{
for(int kx=T[x-1],ky=T[y],l=1,r=m,LR;;)
{
if(l>=r)return l;
int d=v[v[ky].ch[0]].sum-v[v[kx].ch[0]].sum,m=l+(r-l)/2;
if(k>d)k-=d,l=m+1,LR=1;
else r=m,LR=0;
kx=v[kx].ch[LR],ky=v[ky].ch[LR];
}
}
};
动态主席树
struct HJTree
{
int m,siz,tot,T[MAXN],S[MAXN],use[MAXN];
struct Node
{
int ch[2],sum;
} v[MAXN<<5];
void build(int a[],int n,int m)//用a[1]~a[n]建树,a[i]的值不超过m
{
for(this->m=m,siz=tot=1; siz<=n; ++siz)
ins(&(T[siz]=T[siz-1]),a[i]),s[siz]=0;
}
void ins(int *rt,int pos,int val=1)//和静态主席树一样
{
for(int l=1,r=m;;)
{
v[tot]=v[*rt],v[*rt=tot++].sum+=val;
if(l==r)break;
int m=l+(r-l)/2;
if(pos>m)rt=&v[*rt].ch[1],l=m+1;
else rt=&v[*rt].ch[0],r=m;
}
}
void add(int x,int pos,int val)
{
for(; x<siz; x+=x&-x)
ins(&S[x],pos,val);
}
int sum(int x)
{
int ret=0;
for(; x; x-=x&-x)
ret+=v[v[use[x]].ch[0]].sum;
return ret;
}
int ask(int x,int y,int k)
{
for(int i=--x; i; i-=i&-i)use[i]=S[i];
for(int i=y; i; i-=i&-i)use[i]=S[i];
for(int kx=T[x],ky=T[y],l=1,r=m,LR;;)
{
if(l>=r)return l;
int m=l+(r-l)/2,d=sum(y)-sum(x)+v[v[ky].ch[0]].sum-v[v[kx].ch[0]].sum;
if(k>d)k-=d,l=m+1,LR=1;
else r=m,LR=0;
kx=v[kx].ch[LR],ky=v[ky].ch[LR];
for(int i=x; i; i-=i&-i)use[i]=v[use[i]].ch[LR];
for(int i=y; i; i-=i&-i)use[i]=v[use[i]].ch[LR];
}
}
};
无旋Treap
按子树大小分裂
struct FhqTreap
{
struct Node
{
int ch[2],siz,rev;
ll key,val,min,add;
void REV()
{
rev^=1,swap(ch[0],ch[1]);
}
void ADD(ll v)
{
val+=v,min+=v,add+=v;
}
};
vector<Node> v;
int root;
FhqTreap():v(1),root(0) {}
void push_down(int k)
{
if(!k)return;
for(int i=0,*ch=v[k].ch; i<2; ++i)
if(ch[i])
{
v[ch[i]].ADD(v[k].add);
if(v[k].rev)v[ch[i]].REV();
}
v[k].add=v[k].rev=0;
}
void push_up(int k)
{
if(!k)return;
v[k].siz=1,v[k].min=v[k].val;
for(int i=0,*ch=v[k].ch; i<2; ++i)
if(ch[i])
v[k].siz+=v[ch[i]].siz,v[k].min=min(v[k].min,v[ch[i]].min);
}
int merge(int a,int b)
{
if(!a||!b)return a+b;
if(v[a].key<v[b].key)
return push_down(a),v[a].ch[1]=merge(v[a].ch[1],b),push_up(a),a;
return push_down(b),v[b].ch[0]=merge(a,v[b].ch[0]),push_up(b),b;
}
void split(int a,int s,int &l,int &r)
{
if(!s)l=0,r=a;
else if(v[v[a].ch[0]].siz<s)
push_down(a),split(v[a].ch[1],s-v[v[a].ch[0]].siz-1,v[a].ch[1],r),push_up(l=a);
else
push_down(a),split(v[a].ch[0],s,l,v[a].ch[0]),push_up(r=a);
}
void push_back(ll d)
{
v.push_back(Node {{0,0},1,0,rand(),d,d,d});
root=merge(root,v.size()-1);
}
void insert(int x,ll d)
{
v.push_back(Node {{0,0},1,0,rand(),d,d,d});
int a,b,c;
split(root,x-1,a,b);
root=merge(merge(a,v.size()-1),b);
}
void erase(int x)
{
int a,b,c;
split(root,x,a,b),split(a,x-1,a,c),root=merge(a,b);
}
void add(int l,int r,ll d)
{
int a,b,c;
split(root,r,b,c),split(b,l-1,a,b),v[b].ADD(d),root=merge(merge(a,b),c);
}
Node ask(int l,int r)
{
int a,b,c;
split(root,r,b,c),split(b,l-1,a,b);
Node ret=v[b];
return root=merge(merge(a,b),c),ret;
}
void reverse(int l,int r)
{
int a,b,c;
split(root,r,b,c),split(b,l-1,a,b),v[b].REV(),root=merge(merge(a,b),c);
}
void revolve(int l,int r,int d)
{
int a,b,c,e=r-l+1;
split(root,r,b,c),split(b,l-1,a,b),split(b,(e-d%e)%e,b,e);
root=merge(merge(a,merge(e,b)),c);
}
};
按值大小分裂,即排序树
struct FhqTreap
{
struct Node
{
int ch[2],siz;
ll key,val;
};
vector<Node> v;
int root;
FhqTreap():v(1),root(0) {}
void push_up(int k)
{
v[k].siz=v[v[k].ch[0]].siz+v[v[k].ch[1]].siz+1;
}
int merge(int a,int b)
{
if(!a||!b)return a+b;
if(v[a].key<v[b].key)
return v[a].ch[1]=merge(v[a].ch[1],b),push_up(a),a;
return v[b].ch[0]=merge(a,v[b].ch[0]),push_up(b),b;
}
void splitVal(int a,ll w,int &l,int &r)//按值将树划分,使得左子树上的值恰小于w
{
if(!a)l=r=0;
else if(v[a].val>w)splitVal(v[a].ch[0],w,l,v[a].ch[0]),push_up(r=a);
else splitVal(v[a].ch[1],w,v[a].ch[1],r),push_up(l=a);
}
void insert(ll x)
{
int a,b;
v.push_back(Node {{0,0},1,rand(),x});
splitVal(root,x,a,b),root=merge(merge(a,v.size()-1),b);
}
void erase(ll x)
{
int a,b,c;
splitVal(root,x,a,b),splitVal(a,x-1,a,c);
root=merge(merge(a,merge(v[c].ch[0],v[c].ch[1])),b);
}
ll kth(int k)
{
for(int u=root,ls;;)
{
if(ls=v[v[u].ch[0]].siz,ls+1==k)
return v[u].val;
if(ls<k)k-=ls+1,u=v[u].ch[1];
else u=v[u].ch[0];
}
}
int lower_bound(ll x)
{
return upper_bound(x-1);
}
int upper_bound(ll x)
{
int a,b,ret;
return splitVal(root,x,a,b),ret=v[a].siz+1,root=merge(a,b),ret;
}
};
珂朵莉树
Willem, Chtholly and Seniorious
区间加减、第k大、k次方和。数据随机。
#include<bits/stdc++.h>
#define mul(a,b,c) ((a)*(b)%(c))
using namespace std;
typedef long long ll;
typedef pair<int,ll> pil;
ll pow(ll a,ll b,ll m)
{
ll r=1;
for(a%=m; b; a=mul(a,a,m),b>>=1)
if(b&1)r=mul(r,a,m);
return r;
}
struct ChthollyTree:map<int,pil>
{
iterator split(int pos)
{
iterator it=lower_bound(pos);
if(it!=end()&&it->first==pos)return it;
--it;
if(pos>it->second.first)return end();
pair<int,pil> p=*it;
erase(it);
insert(make_pair(p.first,pil(pos-1,p.second.second)));
return insert(make_pair(pos,p.second)).first;
}
void add(int l,int r,ll val)
{
for(iterator b=split(l),e=split(r+1); b!=e; ++b)b->second.second+=val;
}
void set(int l,int r,ll val)
{
erase(split(l),split(r+1)),insert(make_pair(l,pil(r,val)));
}
ll rank(int l,int r,int k)
{
vector<pair<ll,int> > v;
for(iterator b=split(l),e=split(r+1); b!=e; ++b)
v.push_back(make_pair(b->second.second,b->second.first-b->first+1));
sort(v.begin(),v.end());
for(int i=0; i<v.size(); ++i)
if(k-=v[i].second,k<=0)return v[i].first;
return -1;
}
ll sum(int l,int r,ll ex,ll m)
{
ll res=0;
for(iterator b=split(l),e=split(r+1); b!=e; ++b)
res=(res+mul(b->second.first-b->first+1,pow(b->second.second,ex,m),m))%m;
return res;
}
} t;
ll n,m,seed,vmax,M=1e9+7;
ll rnd()
{
ll ret=seed;
seed=(seed*7+13)%M;
return ret;
}
int main()
{
scanf("%lld%lld%lld%lld",&n,&m,&seed,&vmax);
for(int i=1; i<=n; ++i)
t.insert(make_pair(i,pil(i,rnd()%vmax+1)));
for(int i=1; i<=m; ++i)
{
int op=rnd()%4+1,l=rnd()%n+1,r=rnd()%n+1;
if(l>r)swap(l,r);
ll x=rnd()%(op==3?r-l+1:vmax)+1;
if(op==1)t.add(l,r,x);
if(op==2)t.set(l,r,x);
if(op==3)printf("%lld\n",t.rank(l,r,x));
if(op==4)printf("%lld\n",t.sum(l,r,x,rnd()%vmax+1));
}
}
脑洞治疗仪
把一段连续区间的01移动到到另一段区间上,求在某个区间中最大的连续0序列有多大。
#include<bits/stdc++.h>
using namespace std;
typedef int ll;
typedef pair<int,ll> pil;
struct ChthollyTree:map<int,pil>
{
iterator split(int pos)
{
iterator it=lower_bound(pos);
if(it!=end()&&it->first==pos)return it;
--it;
if(pos>it->second.first)return end();
pair<int,pil> p=*it;
erase(it);
insert(make_pair(p.first,pil(pos-1,p.second.second)));
return insert(make_pair(pos,p.second)).first;
}
void set(int l,int r,ll val)
{
erase(split(l),split(r+1)),insert(make_pair(l,pil(r,val)));
}
void scure(int l,int r,int x,int y)
{
iterator e=split(r+1),b=split(l),it=b;
int sum=0;
for(; b!=e; ++b)
if(b->second.second)
sum+=b->second.first-b->first+1;
erase(it,e);
insert(make_pair(l,pil(r,0)));
if(!sum)return;
e=split(y+1),b=split(x),it=b;
if(sum>=y-x+1)
{
erase(b,e);
insert(make_pair(x,pil(y,1)));
return;
}
for( ; b!=e; ++b)
if(!b->second.second)
{
sum-=b->second.first-b->first+1;
if(sum<0)return set(b->first,b->second.first+sum,1);
b->second.second=1;
}
}
ll MAX(int l,int r)
{
iterator e=split(r+1),b=split(l);
ll res=0,now=0;
for(; b!=e; ++b)
if(!b->second.second)
now+=b->second.first-b->first+1;
else if(now)res=max(res,now),now=0;
return max(res,now);
}
} t;
int main()
{
int n,m;
scanf("%d%d",&n,&m);
t.insert(make_pair(1,pil(n,1)));
for(int op,l,r,x,y; m--;)
{
scanf("%d%d%d",&op,&l,&r);
if(op==0)t.set(l,r,0);
else if(op==1)scanf("%d%d",&x,&y),t.scure(l,r,x,y);
else printf("%d\n",t.MAX(l,r));
}
}
莫队
普通莫队
struct Mo
{
struct Query
{
int l,r,id;
bool operator<(const Query& n)const
{
return l/BS!=n.l/BS?l<n.l:r<n.r;
}
};
vector<Query> q;
int L,R;
void query(int l,int r)
{
q.push_back(Query {l,r,q.size()});
}
void rev(int x) {}
void cal(int id) {}
void ask()
{
L=0,R=-1;
sort(q.begin(),q.end());
for(int i=0; i<q.size(); ++i)
{
while(L<q[i].l)rev(L++);
while(L>q[i].l)rev(--L);
while(R<q[i].r)rev(++R);
while(R>q[i].r)rev(R--);
cal(q[i].id);
}
}
};
动态莫队
struct Mo
{
struct Update
{
int pos,NEW,OLD;
};
struct Query
{
int t,l,r,id;
bool operator<(const Query& n)const
{
return l/BS!=n.l/BS?l<n.l:
r/BS!=n.r/BS?r<n.r:t<n.t;
}
};
vector<Update> cq;
vector<Query> q;
int T,L,R;
Mo():cq(1) {}
void query(int x,int y)
{
q.push_back(Query {cq.size()-1,x,y,q.size()});
}
void update(int x,int y)
{
cq.push_back(Update {x,y,t[x]}),t[x]=y;
}
void set(int x,int d)
{
if(vis[x])return rev(x),a[x]=d,rev(x);
a[x]=d;
}
void rev(int x) {}
void cal(int id) {}
void ask()
{
T=L=0,R=-1;
sort(q.begin(),q.end());
for(int i=0; i<q.size(); ++i)
{
while(T<q[i].t)++T,set(cq[T].pos,cq[T].NEW);
while(T>q[i].t)set(cq[T].pos,cq[T].OLD),--T;
while(L<q[i].l)rev(L++);
while(L>q[i].l)rev(--L);
while(R<q[i].r)rev(++R);
while(R>q[i].r)rev(R--);
cal(q[i].id);
}
}
};
树上莫队
按照欧拉序分块,使用Tarjan在生成欧拉序的同时预处理所有询问的lca,预处理时间复杂度
。
h为查询图,即如果有一个询问(u,v),即在h上连
。多个询问边有序插入h。
struct TreeMo:Graph
{
struct Query
{
int l,r,lca,id;
bool operator<(const Query &b)const
{
return l/BS!=b.l/BS?l<b.l:r<b.r;
}
};
vector<Query> q;
vector<int> dfp,dfi,dfo;
UnionFindSet ufs;
Graph h;
int L,R;
TreeMo(int n):Graph(n),h(n),dfp(n*2+1),dfi(n),dfo(n),ufs(n) {}
void query(int x,int y)
{
h.add(Edge {x,y}),h.add(Edge {y,x});
q.push_back(Query {0,0,0,q.size()});
}
void rev(int x) {}
void cal(int id) {}
void dfs(int u,int &cnt)
{
dfp[dfi[u]=++cnt]=u;
for(int i=0,k,to; i<v[u].a.size(); ++i)
if(k=v[u].a[i],to=e[k].to,!dfi[to])
dfs(to,cnt),ufs.merge(u,to);
dfp[dfo[u]=++cnt]=u;
for(int i=0,k,to,id; i<h.v[u].a.size(); ++i)
if(k=h.v[u].a[i],id=k/2,to=h.e[k].to,dfo[to])
{
q[id].lca=ufs.fa(to);
q[id].l=q[id].lca!=u?dfo[u]:dfi[u];
q[id].r=dfi[to];
}
}
void ask(int root=1)
{
dfs(root,BS=0),BS=sqrt(BS);
sort(q.begin(),q.end());
L=0,R=-1;
for(int i=0; i<q.size(); ++i)
{
while(L<q[i].l)rev(dfp[L++]);
while(L>q[i].l)rev(dfp[--L]);
while(R<q[i].r)rev(dfp[++R]);
while(R>q[i].r)rev(dfp[R--]);
if(q[i].lca!=dfp[L])rev(q[i].lca);
cal(q[i].id);
if(q[i].lca!=dfp[L])rev(q[i].lca);
}
}
};
动态树上莫队
struct CapitalTreeMo:Graph
{
struct Update
{
int pos,NEW,OLD;
};
struct Query
{
int t,l,r,lca,id;
bool operator<(const Query &b)const
{
return l/BS!=b.l/BS?l<b.l:
r/BS!=b.r/BS?r<b.r:t<b.t;//在BZOJ4129上去掉r/BS还快100ms?
}
};
vector<Update> cq;
vector<Query> q;
vector<int> dfp,dfi,dfo;
UnionFindSet ufs;
Graph h;
int T,L,R;
CapitalTreeMo(int n):cq(1),Graph(n),h(n),dfp(n*2+1),dfi(n),dfo(n),ufs(n) {}
void query(int x,int y)
{
h.add(Edge {x,y}),h.add(Edge {y,x});
q.push_back(Query {cq.size()-1,0,0,0,q.size()});
}
void update(int x,int y)
{
cq.push_back(Update {x,y,t[x]}),t[x]=y;
}
void dfs(int u,int &cnt)
{
dfp[dfi[u]=++cnt]=u;
for(int i=0,k,to; i<v[u].a.size(); ++i)
if(k=v[u].a[i],to=e[k].to,!dfi[to])
dfs(to,cnt),ufs.merge(u,to);
dfp[dfo[u]=++cnt]=u;
for(int i=0,k,to,id; i<h.v[u].a.size(); ++i)
if(k=h.v[u].a[i],id=k/2,to=h.e[k].to,dfo[to])
{
q[id].lca=ufs.fa(to);
q[id].l=q[id].lca!=u?dfo[u]:dfi[u];
q[id].r=dfi[to];
}
}
void set(int u,int d)
{
if(vis[u])return rev(u),a[u]=d,rev(u);
a[u]=d;
}
void rev(int u) {}
void cal(int id) {}
void ask(int root=1)
{
dfs(root,BS=0),BS=sqrt(BS);
sort(q.begin(),q.end());
T=L=0,R=-1;
for(int i=0; i<q.size(); ++i)
{
while(T<q[i].t)++T,set(cq[T].pos,cq[T].NEW);
while(T>q[i].t)set(cq[T].pos,cq[T].OLD),--T;
while(L<q[i].l)rev(dfp[L++]);
while(L>q[i].l)rev(dfp[--L]);
while(R<q[i].r)rev(dfp[++R]);
while(R>q[i].r)rev(dfp[R--]);
if(q[i].lca!=dfp[L])rev(q[i].lca);
cal(q[i].id);
if(q[i].lca!=dfp[L])rev(q[i].lca);
}
}
};
匹配
KMP
struct KMP
{
const string s;
vector<int> next;
KMP(const string &s):s(s),next(s.size()+1,0)
{
for(int i=1,j; i<s.size(); ++i)
{
for(j=next[i]; j&&s[i]!=s[j]; j=next[j]);
next[i+1]=s[i]==s[j]?j+1:0;
}
}
bool find_in(const string &t)
{
for(int i=0,j=0; i<t.size(); ++i)
{
for(; j&&s[j]!=t[i]; j=next[j]);
if(s[j]==t[i])++j;
if(j==s.size())return 1;//不return可得到t中s的所有匹配地址i+1-s.size()
}
return 0;
}
};
AC自动机
ask之前需要调用getFail()
生成失配函数。
struct AhoCorasick
{
struct Node
{
int ch[26],val,f,last;
int &to(char c)
{
return ch[c-'a'];
}//如果不确定c的范围,使用map
};
vector<Node> v;
AhoCorasick():v(1) {}
void getFail()
{
for(deque<int> q(1,v[0].last=v[0].f=0); !q.empty(); q.pop_front())
for(char c='a'; c<='z'; ++c)
{
int r=q.front(),u=v[r].to(c),w=v[r].f;
if(!r&&u)
{
q.push_back(u);
v[u].f=v[u].last=0;
continue;
}
if(!u)
{
v[r].to(c)=v[w].to(c);
continue;
}
q.push_back(u);
while(w&&!v[w].to(c))w=v[w].f;
v[u].f=v[w].to(c);
v[u].last=v[v[u].f].val?v[u].f:
v[v[u].f].last;
}
}
void add(const string &s,int val,int u=0)
{
for(int i=0; i<s.size(); u=v[u].to(s[i++]))
if(!v[u].to(s[i]))
{
v[u].to(s[i])=v.size();
v.push_back(Node());
}
v[u].val=val;
}
bool find_in(const string &s,int u=0)
{
for(int i=0; i<s.size(); ++i)
if(u=v[u].to(s[i]),
v[u].val||v[u].last)
return 1;
return 0;
}
};
后缀数组
m:字符集大小。
s:字符串,其中最后一位为加入的0。
sa[i]:字典序第i小的是哪个后缀。
rk[i]:后缀i的排名。
h[i]:lcp(sa[i],sa[i−1])。
struct SufArr
{
vector<int> sa,rk,h;
SufArr(const vector<int> &s,int m):sa(s.size(),0),rk(s),h(s.size(),0)
{
vector<int> cnt(s.size()+m,0);
for(int i=0; i<s.size(); ++i)++cnt[rk[i]];
for(int i=1; i<m; ++i)cnt[i]+=cnt[i-1];
for(int i=0; i<s.size(); ++i)sa[--cnt[rk[i]]]=i;
for(int k=1,j=0; k<=s.size()&&j<s.size()-1; k<<=1)
{
for(int i=0; i<s.size(); ++i)
{
if(j=sa[i]-k,j<0)j+=s.size();
h[cnt[rk[j]]++]=j;
}
cnt[0]=sa[h[0]]=j=0;
for(int i=1; i<s.size(); ++i)
{
if(rk[h[i]]!=rk[h[i-1]]||rk[h[i]+k]!=rk[h[i-1]+k])
cnt[++j]=i;
sa[h[i]]=j;
}
swap(rk,sa),swap(sa,h);
}
for(int i=0,k=0,j=rk[0]; i<s.size()-1; ++i,++k)
for(; ~k&&s[i]!=s[sa[j-1]+k]; j=rk[sa[j]+1],--k)
h[j]=k;
}
};
暴力回文
时间复杂度
,常数低,但会被ababababa
这样的数据卡。
int palindrome(const char *s)
{
int ans=0;
for(int i=0,b,e; s[i]; ++i)
{
for(b=i; s[i]==s[i+1];)++i;
for(e=i+1; b&&s[b-1]==s[e];)--b,++e;
if(ans<e-b)ans=e-b;//此时[b,e)为最大回文区间
}
return ans;
}
Manacher线性回文
对于一个位置i,[i−f[i]+1,i+f[i]−1]是最长的以i为中心的奇回文串,g[i]−i是最长的以i为开头的回文串长度。
struct Manacher
{
vector<int> t,f,g;
Manacher(const string &s):t(s.size()+1<<1,0),f(t),g(t)//t初始值为s中没有出现过的值,g开始为0
{
for(int i=0; i<s.size(); ++i)t[i+1<<1]=s[i];
for(int i=1,p=0,m=0; i<t.size(); ++i)
{
for(f[i]=i<m?min(f[2*p-i],m-i):1;
0<i-f[i]&&i+f[i]<t.size()&&
t[i-f[i]]==t[i+f[i]];)
++f[i];
if(m<i+f[i])m=i+f[p=i];
}
for(int i=2; i<t.size(); ++i)
if(g[i-f[i]+1]<i+1)g[i-f[i]+1]=i+1;
for(int i=1; i<t.size(); ++i)
if(g[i]<g[i-1])g[i]=g[i-1];
}
int ask(int l,int r)//多次询问可做一个ST表
{
int ans=0;
for(int i=l+1<<1,e=r+1<<1; i<=e; i+=2)
if(ans<g[i]-i)ans=g[i]-i;
return ans;
}
};
图论
这里用类似邻接表的方法存图。有的算法可能需要邻接矩阵,详见模板·线性代数。
struct Graph
{
struct Vertex
{
vector<int> a,b;//相关出边和入边编号
int siz,dep,top,dfn;//树链剖分中使用,依次代表子树节点数、深度、所在链的顶端节点、dfs序
};
struct Edge
{
int from,to;
ll dist,cap;//边长、容量,图论算法使用
};
vector<Vertex> v;//点集
vector<Edge> e;//边集
Graph(int n):v(n) {}
void add(const Edge &ed)
{
if(ed.from==ed.to)return;//如果有需要请拆点
v[ed.from].a.push_back(e.size());
v[ed.to].b.push_back(e.size());
e.push_back(ed);
}
};
树
struct Tree:Graph
{
Tree(int n):Graph(n) {}
int fa(int k,int i=0)
{
return e[v[k].b[i]].from;
}
int ch(int k,int i=0)
{
return e[v[k].a[i]].to;
}
void build(int u,const Graph &g)//无向图dfs建树,且重边在最前,u为根节点
{
v[u].siz=1;
for(int i=0,w,k; i!=g.v[u].a.size(); ++i)
if(k=g.v[u].a[i],w=g.e[k].to,!v[w].siz)//没访问过的点siz默认0
{
build(w,g);
v[u].siz+=v[w].siz;
add(g.e[k]);
if(v[ch(u)].siz<v[w].siz)//重边移到最前
swap(v[u].a.front(),v[u].a.back());
}
}
};
树链剖分与LCA
struct Diagram:Tree
{
Fenwick data;//暂用树状数组作为默认数据结构
Diagram(const Graph &g,int root):
Tree(g.v.size()),data(g.v.size())
{
build(root,g);
int cnt=v[root].dfn=v[root].dep=1;
dfs(v[root].top=root,cnt);
}
void dfs(int u,int &cnt)
{
for(int i=0,w; i!=v[u].a.size(); ++i)
{
v[w=ch(u,i)].dfn=++cnt;
v[w].top=i?w:v[u].top;
v[w].dep=v[u].dep+1;
dfs(w,cnt);
}
}
int lca(int x,int y)
{
for(; v[x].top!=v[y].top; x=fa(v[x].top))
if(v[v[x].top].dep<v[v[y].top].dep)swap(x,y);
if(v[x].dep<v[y].dep)swap(x,y);
return y;
}
ll ask(int x,int y)
{
ll ans=0;
for(; v[x].top!=v[y].top; x=fa(v[x].top))
{
if(v[v[x].top].dep<v[v[y].top].dep)swap(x,y);
ans+=data.ask(v[v[x].top].dfn,v[x].dfn);
}
if(v[x].dep<v[y].dep)swap(x,y);
return ans+=data.ask(v[y].dfn,v[x].dfn);
}
void add(int x,int y,ll pv)
{
for(; v[x].top!=v[y].top; x=fa(v[x].top))
{
if(v[v[x].top].dep<v[v[y].top].dep)swap(x,y);
data.add(v[v[x].top].dfn,v[x].dfn,pv);
}
if(v[x].dep<v[y].dep)swap(x,y);
data.add(v[y].dfn,v[x].dfn,pv);
}
};
点剖(点分治)
零号点为虚节点。
struct TreeDiv:Graph
{
int root;
vector<int> vis,siz,mx;
TreeDiv(int n):Graph(n),vis(n),siz(n),mx(n,n) {}
void dfsRoot(int u,int fa)
{
for(int i=mx[u]=siz[u]=0,k,to; i<v[u].a.size(); ++i)
if(k=v[u].a[i],to=e[k].to,to!=fa&&!vis[to])
if(dfsRoot(to,u),siz[u]+=siz[to],mx[u]<siz[to])
mx[u]=siz[to];
if(mx[u]<mx[0]-++siz[u])mx[u]=mx[0]-siz[u];
if(mx[root]>mx[u])root=u;
}
void dfsDist(int u,int fa,ll d)
{
//用d更新答案
for(int i=0,k,to; i<v[u].a.size(); ++i)
if(k=v[u].a[i],to=e[k].to,to!=fa&&!vis[to])
dfsDist(to,u,d+e[k].dist);
}
int cal(int u,ll d)//返回符合要求的点对数
{
return dfsDist(u,0,d),/*得到答案*/;
}
void dfs(int u=1)
{
dfsRoot(u,root=0),ans+=cal(u=root,0),vis[u]=1;
for(int i=0,k,to; i<v[u].a.size(); ++i)
if(k=v[u].a[i],to=e[k].to,!vis[to])
ans-=cal(to,e[k].dist),mx[0]=siz[to],dfs(to);
}
};
最小生成树
无向图
同时给出Prim算法(生成新树)、Kruskal算法(消耗小)。
struct Prim:Tree
{
struct DistGreater
{
bool operator()(const Edge &e1,const Edge &e2)
{
return e1.dist>e2.dist;
}
};
ll ans;
vector<int> vis;
priority_queue<Edge,vector<Edge>,DistGreater> q;
Prim(const Graph &g,int root):Tree(n),ans(0),vis(g.v.size(),0)//生成新树,每条边都要有等长反向边
{
for(insert(root,g); !q.empty();)
{
Edge ed=q.top();
if(q.pop(),!vis[ed.to])
{
insert(ed.to,g);
ans+=ed.dist;
add(ed);
}
}
}
void insert(int u,const Graph &g)//把点和对应的相连的边加入集合
{
vis[u]=1;
for(int i=0,k; i!=g.v[u].a.size(); ++i)
if(k=g.v[u].a[i],!vis[g.e[k].to])
q.push(g.e[k]);
}
};
ll kruskal(vector<Edge> &e,int n)//会清空边集e,每条边被认作无向边
{
ll ret=0;
UnionFindSet ufs(n);
for(sort(e.begin(),e.end(),DistGreater()); !e.empty(); e.pop_back())
if(ufs.fa(e.back().from)!=ufs.fa(e.back().to))
{
ufs.merge(e.back().from,e.back().to);
ret+=e.back().dist;
}
return /*ufs.siz>1?INF:*/ret;//视情况选择去注释
}
有向图
指定以root为根,如果没有限定根那么新建一个虚拟点作为根,向所有边连边长最大边长+1的边,在最后生成的图中去掉此边。时间复杂度 。
ll zhuLiu(vector<Edge> &e,int root,int n)//不存在返回INF
{
for(ll ret=0;;)
{
vector<ll> in(n,INF);
vector<int> pre(n,NPOS);
for(int i=0,to; i<e.size(); ++i)
{
if(e[i].from==(to=e[i].to))
swap(e[i--],e.back()),e.pop_back();
else if(in[to]>e[i].dist)
in[to]=e[i].dist,pre[to]=e[i].from;
}
for(int i=in[root]=0; i<n; ++i)
if(in[i]==INF)return INF;
vector<int> id(n,NPOS),vis(n,NPOS);
int tn=0;
for(int i=0,v; i<n; ++i)
{
for(ret+=in[v=i]; vis[v]!=i&&id[v]==NPOS&&v!=root; v=pre[v])
vis[v]=i;
if(v!=root&&id[v]==NPOS)
{
for(int u=pre[v]; u!=v; u=pre[u])
id[u]=tn;
id[v]=tn++;
}
}
if(!tn)return ret;
for(int i=0; i<n; ++i)
if(id[i]==NPOS)id[i]=tn++;
for(int i=0,v; i<e.size(); ++i)
if((e[i].from=id[e[i].from])!=(e[i].to=id[v=e[i].to]))
e[i].dist-=in[v];
n=tn,root=id[root];
}
}
连通性
无向图求割和双连通分量
割边:在连通图中,删除了连通图的某条边后,图不再连通。这样的边被称为割边,也叫做桥。
割点:在连通图中,删除了连通图的某个点以及与这个点相连的边后,图不再连通。这样的点被称为割点。
构造dfs搜索树,在树上有两类节点可以成为割点:
对根节点u,若其有两棵或两棵以上的子树,则该根结点u为割点;
对非根非叶节点u,若其中的某棵子树的节点均没有指向u的祖先节点的回边,说明删除u之后,根结点与该棵子树的节点不再连通;则节点u为割点。
对于一个无向图的子图,当删除其中任意一条边后,不改变图内点的连通性,这样的子图叫做边的双连通子图。而当子图的边数达到最大时,叫做边的双连通分量。原理是图中所有割边再求一次SCC,可直接使用下面求SCC的代码。
对于一个无向图的子图,当删除其中任意一个点后,不改变图内点的连通性,这样的子图叫做点的双连通子图。而当子图的边数达到最大时,叫做点的双连通分量。下面给出求点双连通分量的代码。
struct BCC:Graph//Biconnected Connected Componenet
{
vector<int> low,bid,stak,cutPoint,cutEdge;//连通块最早dfs序,边的端点所属双连通块
int bcc_siz;
BCC(int n):Graph(n) {}
void ask()
{
low.assign(v.size(),NPOS);
bid.assign(e.size(),NPOS);
cutPoint.assign(v.size(),0);
cutEdge.assign(e.size(),0);
for(int i=bcc_siz=0,cnt=0; i<v.size(); ++i)
if(low[i]==NPOS)
dfs(i,NPOS,cnt);
}
void dfs(int u,int fa,int &cnt)
{
low[u]=v[u].dfn=++cnt;
for(int i=0,k,to,ch=0; i<v[u].a.size(); ++i)
if(k=v[u].a[i],to=e[k].to,to!=fa)
{
if(low[to]==NPOS)
{
++ch;
stak.push_back(k);
dfs(to,u,cnt);
low[u]=min(low[u],low[to]);
if(low[to]>=v[u].dfn)
for(++bcc_siz,cutPoint[u]=fa!=NPOS||ch>1;;)
{
int x=stak.back();
stak.pop_back();
bid[x]=bid[x^1]=bcc_siz-1;
if(x==k)break;
}
if(low[to]>v[u].dfn)cutEdge[k]=cutEdge[k^1]=1;
}
else if(v[to].dfn<v[u].dfn)
{
stak.push_back(k);
low[u]=min(low[u],v[to].dfn);
}
}
}
};
双连通图的构造
先求出所有的桥,然后删除这些桥边,剩下的每个连通块都是一个双连通子图。把每个双连通子图收缩为一个顶点,再把桥边加回来,最后的这个图一定是一棵树,边连通度为1。统计出树中度为1的节点的个数,即为叶节点的个数,记为leaf
。至少在树上添加(leaf+1)/2
条边,就能使树达到边双连通:先把两个最近公共祖先最远的两个叶节点之间连接一条边,这样可以把这两个点到祖先的路径上所有点收缩到一起,因为一个形成的环一定是双连通的;然后再找两个最近公共祖先最远的两个叶节点,这样一对一对找完,恰好是(leaf+1)/2
次,把所有点收缩到了一起。
有向图求强连通分量
如果是无向图,求出来的还是边双连通分量。
struct SCC:Graph//Strongly Connected Componenet
{
vector<int> low,sid,stak;//连通块最早dfs序,点所属连通块
int scc_siz;
SCC(int n):Graph(n) {}
void ask()
{
low.assign(v.size(),NPOS);
sid.assign(v.size(),NPOS);
for(int i=scc_siz=0,cnt=0; i!=v.size(); ++i)
if(low[i]==NPOS)
dfs(i,NPOS,cnt);
}
void dfs(int u,int fa,int &cnt)
{
low[u]=v[u].dfn=++cnt;
stak.push_back(u);
for(int i=0,k,to; i!=v[u].a.size(); ++i)
if(k=v[u].a[i],to=e[k].to,to!=fa,1)//求边双连通分量把",1"注释掉,即不许走回边
{
if(low[to]==NPOS)
dfs(to,u,cnt),low[u]=min(low[u],low[to]);
else if(sid[to]==NPOS)
low[u]=min(low[u],v[to].dfn);
}
if(low[u]==v[u].dfn)
for(++scc_siz;;)
{
int x=stak.back();
stak.pop_back();
sid[x]=scc_siz-1;
if(x==u)break;
}
}
};
2-SAT
n个布尔变量 ,逻辑表达式 ,其中 ,判断是否存在 的取值使得Y值为1。因为 ,所以对于一个要求 ,我们连 两条边。如果有一条边 ,意味着如果A成立那么B必然成立。若 同一SCC,则不存在。
最短路
Floyed多源最短路&传递闭包
struct Floyed:Matrix
{
void ask()//不连通置INF
{
for(int k=0; k<n; ++k)
for(int i=0; i<n; ++i)
for(int j=0; j<n; ++j)
if(a[i][j]>a[i][k]+a[k][j])
a[i][j]=a[i][k]+a[k][j];
}
};
Dijkstra算法
适用于边权为正的情况(无论有向图还是无向图),用于求单源最短路。
直接给出其优先队列优化的版本。另外,由于priority_queue
并不提供修改优先级的操作,为避免重复扩展,这里选择将新元素直接插入队列并在运行时判断该点是否被处理,并不影响结果的正确性。
struct Dijkstra:Graph
{
vector<ll> d;
vector<int> p;
Dijkstra(int n):Graph(n) {}
void ask(int s)
{
d.assign(v.size(),INF);
p.assign(v.size(),NPOS);
priority_queue<pair<ll,int> > q;
for(q.push(make_pair(d[s]=0,s)); !q.empty();)
{
ll dis=-q.top().first;
int u=q.top().second;
if(q.pop(),d[u]<dis)continue;
for(int i=0,k,to; i!=v[u].a.size(); ++i)
if(k=v[u].a[i],to=e[k].to,
d[to]>d[u]+e[k].dist)
{
d[to]=d[u]+e[k].dist,p[to]=k;
q.push(make_pair(-d[to],to));
}
}
}
};
BellmanFord算法
直接给出其队列优化、国内称之为SPFA算法的版本。较之Dijkstra算法,此算法不够快速稳定但是可以允许负边存在,当s到达负权回路时会直接返回0。
struct BellmanFord:Graph
{
vector<ll> d;
vector<int> p;
BellmanFord(int n):Graph(n) {}
bool ask(int s)
{
d.assign(v.size(),INF);
p.assign(v.size(),NPOS);
vector<int> cnt(v.size(),0),flag(v.size(),d[s]=0);
for(deque<int> q(cnt[s]=flag[s]=1,s); !q.empty(); q.pop_front())
for(int u=q.front(),i=flag[u]=0,k,to; i!=v[u].a.size(); ++i)
if(k=v[u].a[i],to=e[k].to,
d[to]>d[u]+e[k].dist)
{
d[to]=d[u]+e[k].dist,p[to]=k;
if(!flag[to])
{
if(v.size()==++cnt[to])return 0;
flag[to]=1,q.push_back(to);
}
}
return 1;
}
};
差分约束系统
按如下方式建图、跑SPFA:
对每个不等式
,从
向
连一条边,边长为
。
若不等号的方向相反,即
,则在不等式两边同时乘以
,变成
,即从
到
连一条边,边长为
。
Astar求k短路
朴素的想法是使用priority_queue
从原点出发向外探索,当取出终点t第k次时就得到第k短路,类似bfs的思想,缺陷是越往后状态数越多。
我们在反向图上从
跑Astar算法,通过优先展开到s近的状态,使搜索方向靠近答案,而不是一层一层全都展开,估价函数
,f是估计的s到t的距离,g是到达当前点已经点的花费,h是预计剩下的花费。这里h取当前点的距离到s距离,可通过从s跑一遍Dijkstra可以预处理得出。
Astar算法是只有到达终点的时候才能统计答案,这导致可能拓展很多个状态才能得到一个用来更新答案的有效状态。例如一个n元环,当我们到达终点之后,可能还要拓展n次才能得到下一个状态,于是时间复杂度就被卡到
。
k下标从0开始,即最短路==第0短路;有的坑题需要在调用前补一句if(s==t)++k;
。
struct Astar:Dijkstra
{
vector<ll> ans;
Astar(int n):Dijkstra(n) {}
void ask(int s,int t,int k)
{
Dijkstra::ask(s);
ans.assign(k,INF);
if(d[t]==INF)return;
vector<int> cnt(v.size(),0);
priority_queue<pair<ll,int> > q;
for(q.push(make_pair(-d[t],t)); cnt[s]<k&&!q.empty();)
{
ll dis=-q.top().first;
int u=q.top().second;
if(u==s)ans[cnt[s]]=dis;
if(q.pop(),++cnt[u]>k)continue;
for(int i=0,k; i<v[u].b.size(); ++i)
k=v[u].b[i],q.push(make_pair(d[u]-d[e[k].from]-e[k].dist-dis,e[k].from));
}
}
};
可持久化堆求k短路
比Astar相比,有稳定时间复杂度 。
//待补充
网络流
ISAP求最大流
struct ISAP:Graph
{
ll flow;
vector<ll> f;
vector<int> h,cur,gap;
ISAP(int n):Graph(n) {}
void add(Edge ed)
{
Graph::add(ed);
swap(ed.from,ed.to),ed.cap=0;
Graph::add(ed);
}
ll dfs(int s,int u,int t,ll r)
{
if(r==0||u==t)return r;
ll _f,_r=0;
for(int &i=cur[u],k; i<v[u].a.size(); ++i)
if(k=v[u].a[i],h[u]==h[e[k].to]+1)
{
_f=dfs(s,e[k].to,t,min(r-_r,e[k].cap-f[k]));
f[k]+=_f,f[k^1]-=_f,_r+=_f;
if(_r==r||h[s]>=v.size())return _r;
}
if(!--gap[h[u]])h[s]=v.size();
return ++gap[++h[u]],cur[u]=0,_r;
}
void ask(int s,int t)
{
h.assign(v.size(),0);
cur.assign(v.size(),0);
gap.assign(v.size()+2,0);
/*for(deque<int> q(h[t]=gap[t]=1,t); !q.empty(); q.pop_front())//可选预处理
for(int i=0,u=q.front(),k,to; i<v[u].a.size(); ++i)
if(to=e[v[u].a[i]].to,!h[to])
++gap[h[to]=h[u]+1],q.push_back(to);*/
for(f.assign(e.size(),flow=0); h[s]<v.size();)
flow+=dfs(s,s,t,INF);
}
};
ZKW求费用流
定义一条边的费用为流量*边长,求总费用最小的最大流。
对于最终流量较大,而费用取值范围不大的图,或者是增广路径比较短的图(如二分图),zkw算法都会比较快,原因是充分发挥优势。比如流多说明可以同一费用反复增广,费用窄说明不用改太多距离标号就会有新增广路,增广路径短可以显著改善最坏情况,因为即使每次就只增加一条边也可以很快凑成最短路。如果恰恰相反,流量不大,费用不小,增广路还较长,就不适合 zkw 算法了。
struct ZKW:Graph
{
ll flow,cost;
vector<ll> h,f;
vector<int> vis;
ZKW(int n):Graph(n) {}
void add(Edge ed)
{
Graph::add(ed);
swap(ed.from,ed.to),ed.cap=0,ed.dist*=-1;
Graph::add(ed);
}
ll dfs(int u,int t,ll r)
{
if(r==0||u==t)return r;
if(vis[u])return 0;
ll _f=vis[u]=1,_r=0;
for(int i=0,k; r>_r&&i<v[u].a.size(); ++i)
if(k=v[u].a[i],h[e[k].to]+e[k].dist==h[u])
_f=dfs(e[k].to,t,min(r-_r,e[k].cap-f[k])),f[k]+=_f,f[k^1]-=_f,_r+=_f;
return _r;
}
void ask(int s,int t)
{
h.assign(v.size(),0);
vis.assign(v.size(),0);
for(f.assign(e.size(),flow=cost=0);;)
{
ll _f=dfs(s,t,INF),d=INF;
flow+=_f,cost+=_f*h[s];
for(int u=0; u<v.size(); ++u)
for(int i=0,k; vis[u]&&i<v[u].a.size(); ++i)
if(k=v[u].a[i],!vis[e[k].to]&&e[k].cap>f[k])
d=min(d,e[k].dist+h[e[k].to]-h[e[k].from]);
if(d==INF)return;
for(int i=0; i<v.size(); ++i)if(vis[i])h[i]+=d,vis[i]=0;
}
}
};
EK算法求费用流
BellmanFord算法找增广路,可能被卡但是可以跑负费用流(最大费用流)。
struct EdmondKarp:Graph
{
ll flow,cost;
vector<ll> f;
EdmondKarp(int n):Graph(n) {}
void add(Edge ed)
{
Graph::add(ed);
swap(ed.from,ed.to),ed.cap=0,ed.dist*=-1;
Graph::add(ed);
}
void ask(int s,int t)
{
vector<int> p(v.size(),NPOS);
for(f.assign(e.size(),flow=cost=0);;)
{
vector<ll> d(v.size(),INF);
vector<int> flag(v.size(),d[s]=0);
for(deque<int> q(flag[s]=1,s); !q.empty(); q.pop_front())
for(int u=q.front(),i=flag[u]=0,k,to; i<v[u].a.size(); ++i)
if(k=v[u].a[i],to=e[k].to,
e[k].cap>f[k]&&d[to]>d[u]+e[k].dist)
{
d[to]=d[u]+e[k].dist,p[to]=k;
if(!flag[to])q.push_back(to),flag[to]=1;
}
if(d[t]==INF)return;
ll _f=INF;
for(int u=t; u!=s; u=e[p[u]].from)
_f=min(_f,e[p[u]].cap-f[p[u]]);
for(int u=t; u!=s; u=e[p[u]].from)
cost+=_f*e[p[u]].dist,f[p[u]]+=_f,f[p[u]^1]-=_f;
flow+=_f;
}
}
};
PD算法求费用流
性能优秀,只能跑非负权图。
struct PrimalDual:Graph
{
ll flow,cost;
vector<ll> f;
PrimalDual(int n):Graph(n) {}
void add(Edge ed)
{
Graph::add(ed);
swap(ed.from,ed.to),ed.cap=0,ed.dist*=-1;
Graph::add(ed);
}
void ask(int s,int t)//询问s到t的最小费用最大流,答案存在flow、cost中
{
vector<int> p(v.size(),NPOS);
vector<ll> d(v.size(),INF),h(v.size(),0);
for(f.assign(e.size(),flow=cost=0);;)
{
priority_queue<pair<ll,int> > q;
for(q.push(make_pair(d[s]=0,s)); !q.empty();)
{
ll dis=-q.top().first;
int u=q.top().second;
if(q.pop(),d[u]<dis)continue;
for(int i=0,k,to; i<v[u].a.size(); ++i)
if(k=v[u].a[i],to=e[k].to,
e[k].cap>f[k]&&d[to]>d[u]+e[k].dist+h[u]-h[to])
{
d[to]=d[u]+e[k].dist+h[u]-h[to],p[to]=k;
q.push(make_pair(-d[to],to));
}
}
if(d[t]==INF)return;
for(int i=0; i<d.size(); ++i)
if(d[i]!=INF)h[i]+=d[i],d[i]=INF;
ll _f=INF;
for(int u=t; u!=s; u=e[p[u]].from)
_f=min(_f,e[p[u]].cap-f[p[u]]);
for(int u=t; u!=s; u=e[p[u]].from)
cost+=_f*e[p[u]].dist,f[p[u]]+=_f,f[p[u]^1]-=_f;
flow+=_f;
}
}
};
上下界有源汇网络流
T向S连容量正无穷的边,将有源汇转化为无源汇。每条边容量减去下界,设
表示流入i的下界之和减去流出i的下界之和。新建超级源汇SS,TT,对于
的点,SS向i连容量为
的边。对于
的点,i向TT连容量为
的边。
求出以 SS,TT 为源汇的最大流,如果等于
则存在可行流。再求出以S,T为源汇的最大流即为最大流。
费用流:建完图后等价于求以SS,TT为源汇的的费用流。
上下界费用流:先把下界的费用加入答案。
线性规划转费用流
首先添加松弛变量,将不等号都变为等号。分别用下一个式子减去上一个式子,如果每个变量只出现了两次且符号一正一负,那么可以转化为费用流。对于每个式子建立一个点,那么每个变量对应一条边,从一个点流出,向另一个点流入。这样,对于等式右边的常数,如果是正的,对应从源点向该点连一条流量C,费用0的边;如果是负的对应从该点向汇点连一条流量−C,费用0的边。对于每个变量,从它系数为正的式子向系数为负的式子连一条容量为 inf,费用为它在目标函数里系数的边。这样网络流模型就构造完毕了。
判断边是否属于某一最小割集
在残余网络 (还有流量的边) 中求强连通分量,顶点不在同一 SCC 且满流的边。
判断边是否为全部最小割集的边: 在上一条的基础上,还要满足起点与 S 在同一 SCC,且终点与T在同一SCC。
二分图
- 二分图的一个等价定义是:不含有含奇数条边的环的图。
- 完美匹配图中所有的顶点都是匹配点。
- 二分图的最小覆盖分为最小顶点覆盖和最小路径覆盖:
- 最小点覆盖(最小割)是指最少的顶点数使得二分图G中的每条边都至少与其中一个点相关联。二分图中,最小割=最大匹配。
- 最小边覆盖(最大独立集)是指用尽量少的不相交简单路径覆盖二分图中的所有顶点。二分图中,最小点覆盖+最小边覆盖=总点数。
- Hall定理:二分图中的两部分顶点组成的集合分别为 X,Y ,则有一组无公共点的边,一端恰好为组成 X 的点的充分必要条件是:X 中的任意 k 个点至少与 Y 中的 k 个点相邻。对于区间图只需要考虑极端情况,线段树维护。
匈牙利算法求最大匹配
左边nl个点
,右边nr个点
,取n=max(nl,nr)
,左i右j间代价a[x][y]
。
生成fl[i]
表示左边第i个匹配右边第fl[i]
个(对应权a[i][fl[i]]
),fr同理。时间复杂度
。
稀疏图的时候考虑用邻接表代替邻接矩阵,并且找完全匹配的时候有问题可直接return。
匹配是一个边集,其中任意两条边都没有公共顶点;扫一遍fl
或fr
判断有多少不等于NPOS
即为最大匹配数。
struct Hungary:Matrix
{
int fl[N],fr[N],vr[N];
bool dfs(int x,int rt)
{
for(int y=0; y<n; ++y)
if(a[x][y]&&vr[y]!=rt)
if(vr[y]=rt,fr[y]==NPOS||dfs(fr[y],rt))
return fl[fr[y]=x]=y,1;
return 0;
}
void ask()
{
fill(fl,fl+n,NPOS),fill(fr,fr+n,NPOS),fill(vr,vr+n,NPOS);
for(int i=0; i<n; ++i)if(fl[i]==NPOS)dfs(i,i);
}
}
HK算法求最大匹配
时间复杂度 ,稀疏图上效果明显。
struct HopcroftKarp
{
vector<int> g[N];
int n,fl[N],fr[N],hl[N],hr[N],q[N];
bool dfs(int x)
{
for(int i=0,c=hl[x]+1,y=hl[x]=NPOS; i<g[x].size(); ++i)
if(hr[y=g[x][i]]==c)
if(hr[y]=NPOS,fr[y]==NPOS||dfs(fr[y]))
return fl[fr[y]=x]=y,1;
return 0;
}
void ask()
{
fill(fl,fl+n,NPOS),fill(fr,fr+n,NPOS);
for(int x=0; x<n; ++x)
for(int i=0,y; i<g[x].size(); ++i)
if(fr[y=g[x][i]]==NPOS)
{
fl[fr[y]=x]=y;
break;
}
for(int ql,qr,ok;;)
{
fill(hl,hl+n,NPOS),fill(hr,hr+n,NPOS);
for(int x=ql=qr=ok=0; x<n; ++x)if(fl[x]==NPOS)hl[q[qr++]=x]=0;
while(ql<qr)
for(int i=0,x=q[ql++],y; i<g[x].size(); ++i)
if(hr[y=g[x][i]]==NPOS)
{
hr[y]=hl[x]+1;
if(fr[y]==NPOS)ok=1;
else if(hl[fr[y]]==NPOS)hl[q[qr++]=fr[y]]=hr[y]+1;
}
if(!ok)return;
for(int x=0; x<n; ++x)if(fl[x]==NPOS)dfs(x);
}
}
};
KM算法求最优完备匹配
最大费用流时,a初始化0;最大费用最大流时,a初始化-N*INF
。
struct KuhnMunkres:Matrix
{
ll hl[N],hr[N],slk[N];
int fl[N],fr[N],vl[N],vr[N],pre[N],q[N],ql,qr;
int check(int i)
{
if(vl[i]=1,fl[i]!=NPOS)return vr[q[qr++]=fl[i]]=1;
while(i!=NPOS)swap(i,fr[fl[i]=pre[i]]);
return 0;
}
void bfs(int s)
{
fill(slk,slk+n,INF),fill(vl,vl+n,0),fill(vr,vr+n,0);
for(vr[q[ql=0]=s]=qr=1;;)
{
for(ll d; ql<qr;)
for(int i=0,j=q[ql++]; i<n; ++i)
if(!vl[i]&&slk[i]>=(d=hl[i]+hr[j]-a[i][j]))
if(pre[i]=j,d)slk[i]=d;
else if(!check(i))return;
ll d=INF;
for(int i=0; i<n; ++i)
if(!vl[i]&&d>slk[i])d=slk[i];
for(int i=0; i<n; ++i)
{
if(vl[i])hl[i]+=d;
else slk[i]-=d;
if(vr[i])hr[i]-=d;
}
for(int i=0; i<n; ++i)
if(!vl[i]&&!slk[i]&&!check(i))return;
}
}
void ask()
{
fill(fl,fl+n,NPOS),fill(fr,fr+n,NPOS),fill(hr,hr+n,0);
for(int i=0; i<n; ++i)hl[i]=*max_element(a[i],a[i]+n);
for(int j=0; j<n; ++j)bfs(j);
}
};
关键点
关键点即一定在最大匹配中的点。
求出任意一个最大匹配,那么只需要考虑哪些匹配点不一定在。
假设是考虑左侧的点,右侧类似:
将匹配边从右往左,非匹配边从左到右,从左侧每个未匹配点开始DFS到的匹配点都不是关键点。
关键边
求出任意一个最大匹配,将匹配边从右到左,剩余边从左到右,求出 SCC。 对于一条边:
若它位于当前匹配中,那么若两端点位于同一SCC,则是可能在,否则必定在;若它不位于当前匹配中,那么若两端点位于同一 SCC,则是可能在,否则必定不在。
带花树求最大匹配
struct Blossom:Graph
{
vector<int> f,vis,pre,flag;
deque<int> q;
UnionfindSet ufs;
Blossom(int n):Graph(n) {}
void poi(int x,int a)
{
for(int y,z; x!=a; ufs.merge(y,x),ufs.merge(x=z,y))
{
if(ufs.fa(z=pre[y=f[x]])!=a)pre[z]=y;
if(!flag[y])flag[y]=1,q.push_back(y);
if(!flag[z])flag[z]=1,q.push_back(z);
}
}
void ask()
{
f.assign(v.size(),NPOS),vis=f;
for(int s=0,t=0; s<v.size(); ++s)
if(f[s]==NPOS)
for(ufs.init(v.size()),pre.assign(v.size(),NPOS),flag=pre,q.assign(flag[s]=1,s);
f[s]==NPOS&&!q.empty(); q.pop_front())
for(int i=0,x=q.front(),y,a,b; i<v[x].a.size(); ++i)
if((y=e[v[x].a[i]].to)!=f[x]&&flag[y]&&ufs.fa(x)!=ufs.fa(y))
{
if(flag[y]==1)
{
for(a=x,b=y,++t;; swap(a,b))
if(a!=NPOS)
{
if(vis[a=ufs.fa(a)]==t)break;
vis[a]=t,a=f[a]!=NPOS?pre[f[a]]:NPOS;
}
if(ufs.fa(x)!=a)pre[x]=y;
if(ufs.fa(y)!=a)pre[y]=x;
poi(x,a),poi(y,a);
}
else if(f[y]==NPOS)
{
for(pre[y]=x; y!=NPOS;)swap(y,f[f[y]=pre[y]]);
break;
}
else pre[y]=x,q.push_back(f[y]),flag[f[y]]=1,flag[y]=0;
}
}
};
欧拉路
给定无孤立结点图G,若存在一条路,经过图中每边一次且仅一次,该条路称为欧拉路。
- 无向图:当仅当该图所有顶点的度数为偶数(此时为回路),或除两个度数为奇数外(作为路径的起点和终点)、其余全为偶数。
- 有向图:当仅当该图所有顶点出度=入度(此时为回路),或一个顶点出度=入度+1(作为起点)、另一个顶点入度=出度+1(作为终点)、其他顶点出度=入度。
struct Fleury:Graph
{
vector<int> vis,cur,p;
Fleury(int n):Graph(n) {}
void dfs(int u)
{
for(int &i=cur[u],k; i<v[u].b.size(); ++i)//遍历原图的反向图,这里加了一个“当前弧”优化
if(k=v[u].b[i],!vis[k])
{
vis[k]/*=vis[k^1]=*/1;//无向图去掉注释,即同时标记反向边
dfs(e[k].from);
p.push_back(k);
}
}
void ask()//查询欧拉回路,路径上边的序号按顺序存在p中
{
vis.assign(e.size(),0),cur.assign(v.size(),0),p.clear();
for(int i=0; i<v.size(); ++i)
if(v[i].b.size()%2)
return dfs(i);
dfs(0);
}
};
混合图欧拉回路判定
首先给无向边随便定一个方向,设 为x连出去的边数−连入x的边数。若存在 为奇数,或者图不连通,则无解。否则建立源点S,汇点T。对于一个点x,若 ,则S向x连边,容量 ;若 ,则x向T连边,容量 。 对于一条定了向的无向边 ,x向y连边,容量1,求出最大流,若与 S 和T连的每条边都满流,则有解。
数论
辗转相除法与同余系
裴蜀定理:对任何
和它们的最大公约数
,关于未知数
的线性不定方程(称为裴蜀等式):
当仅当
,可知有无穷多解。特别地,
一定有解。
推论:
互质的充要条件是
有整数解。
ll gcd(ll a,ll b)//a、b的最大公约数
{
return b?gcd(b,a%b):a;
}
ll lcm(ll a,ll b)//a、b的最小公倍数
{
return a/gcd(a,b)*b;
}
ll gcd(ll a,ll b,ll &x,ll &y)//扩展欧几里得,引用返回a*x+b*y=gcd(a,b)绝对值之和最小的解
{
if(!a)return x=0,y=1,b;
ll d=gcd(b%a,a,y,x);
return x-=y*(b/a),d;
}
同余系运算
求乘法逆元的另外一种方法是用欧拉定理
,x的逆是
。特别地,m为素数时
,此时x的逆就是pow(x,m-2,m)
。
log函数:m为素数时求解模方程
。设P为质数,G为P的原根,则
等价于
,其中
。
ll add(ll a,ll b,ll m)
{
if(a+=b,a<0)a=m-(-a%m);//负数取模和编译器有关,这样才是需要的同余系运算
return a%m;
}
ll mul(ll a,ll b,ll m)//根据a*b是否爆ll替换a*b%m
{
ll r=0;
for(a%=m; b; b>>=1,a=add(a,a,m))
if(b&1)r=add(r,a,m);
return r;
}
ll pow(ll a,ll b,ll m)
{
ll r=1;
for(a%=m; b; b>>=1,a=mul(a,a,m))
if(b&1)r=mul(r,a,m);
return r;
}
ll sub(ll a,ll b,ll m)
{
return add(a,-b,m);
}
ll inv(ll a,ll m)//模m下a的乘法逆元,不存在返回-1(m为素数时a不为0必有逆元)
{//return pow(a,phi(m)-1,m);
ll x,y,d=gcd(a,m,x,y);
return d==1?(x+m)%m:-1;
}
ll div(ll a,ll b,ll m)
{
return mul(a,inv(b,m),m);
}
ll log(ll a,ll b,ll m)
{
ll n=ceil(sqrt(m+0.5));
map<ll,ll> x;
for(ll i=0,e=1; i<n; e=mul(e,a,m),++i)
if(!x.count(e))x[e]=i;
for(ll i=0,v=inv(pow(a,n,m),m); i<n; ++i,b=mul(b,v,m))
if(x.count(b))return i*n+x[b];
return -1;
}
同余方程
void sol(ll a,ll b,ll n,vector<ll> &ans)//返回ax=b(mod n)循环节内所有解
{
ll x,y,d=gcd(a,n,x,y);
if(ans.clear(),b%d)return;
ans.push_back((b/d)%(n/d)*(x=(x%n+n)%n));
for(int i=1; i<d; ++i)
ans.push_back((ans[0]+i*n/d)%n);
}
同余方程组
ll sol(const vector<pair<ll,ll> > &v)//x%v[i].first==v[i].second,不存在返回-1
{
ll m=v[0].first,r=v[0].second,c,d,x,y,z;
for(int i=1; i<v.size(); ++i)
{
if(c=v[i].second-r,d=gcd(m,v[i].first,x,y),c%d)
return -1;
gcd(m/d,z=v[i].first/d,x,y),r+=c/d*x%z*m,r%=m*=z;
}
return r<0?r+m:r;
}
欧拉筛
欧拉函数
是小于n的正整数中与n互素的数的数目。特别地,规定
,易知n>2时都为偶数。
欧拉函数是积性函数,即对任意素数
满足下列关系:
对任何两个互质的正整数
有欧拉定理:
当m为素数p时,此式变为费马小定理:
利用欧拉函数和它本身不同质因数的关系,用筛法
预处理某个范围内所有数的欧拉函数值,并求出素数表。同时,利用计算欧拉函数过程中求出的最小素因子m,可以实现
的素因数分解。
更新:增加同时求莫比乌斯函数
的代码,存在mu
中
struct EulerSieve
{
vector<int> p,m,phi,mu;//素数序列,最小素因子,欧拉函数,莫比乌斯函数
EulerSieve(int N):m(N,0),phi(N,0),mu(N,0)
{
phi[1]=mu[1]=1;//m[1]=0
for(long long i=2,k; i<N; ++i)//防i*p[j]爆int
{
if(!m[i])p.push_back(m[i]=i),phi[i]=i-1,mu[i]=-1;//i是素数
for(int j=0; j<p.size()&&(k=i*p[j])<N; ++j)
{
phi[k]=phi[i]*p[j];
if((m[k]=p[j])==m[i])
{
mu[k]=0;
break;
}
phi[k]-=phi[i];
mu[k]=-mu[i];
}
}
}
};
常见数论函数变换
莫比乌斯反演
若
,则
若
,则
(此时代
可得上式)
举例(其中
):
可以使用线性筛预处理处理,我们就可以枚举
求上式了,时间复杂度
。多组数据
询问上式,时间复杂度就变成了
。事实上,
是不会轻易变化的,是过了连续的一段后才发生变化的,那么我们就可以计算出这一段的结束位置,对
函数作前缀和,就可以直接分块了,这样的时间复杂度是
的。
前缀和
欧拉函数前缀和
莫比乌斯函数前缀和
PollardRho大数素因子分解
时间复杂度 ,数据多的时候可考虑欧拉筛优化。
struct PollardRho
{
bool isPrime(ll n,int S=12)//MillerRabin素数测试,S为测试次数,用前S个素数测一遍,S=12可保证unsigned long long范围内无错;n<2请特判
{
ll d,u,t,p[]= {2,3,5,7,11,13,17,19,23,29,31,37};
for(d=n-1; !(d&1);)d>>=1;//未对0,1做特判!
for(int i=0; i<S; ++i)
{
if(n%p[i]==0)return n==p[i];
for(u=d,t=pow(p[i],u,n); u!=n-1&&t!=n-1&&t!=1; u<<=1)
t=mul(t,t,n);
if(t!=n-1&&!(u&1))return 0;
}
return 1;
}
void fac(ll n,vector<ll> &factor)
{
if(isPrime(n))return factor.push_back(n);
for(ll c=1;; ++c)
for(ll i=0,k=1,x=rand()%(n-1)+1,y,p;;)
{
if(++i==k)y=x,k<<=1;
if(x=(mul(x,x,n)+c)%n,p=gcd(abs(x-y),n),p==n)break;
if(p>1)return fac(p,factor),fac(n/p,factor);
}
}
};
快速傅里叶变换(FFT)
template<typename T>
void rader(vector<T> &x)///雷德算法,x.size()为2的整数次幂,将 x[i]与x[j]交换,其中j为“i的二进制反转之后”的数
{
for(int i=0,j=0,k; i<x.size()-1; ++i,j+=k)
{
if(i<j)swap(x[i],x[j]);
for(k=x.size()>>1; k<=j; k>>=1)j-=k;
}
}
void fft(vector<complex<double> > &x,int inv)//x.size()为2的整数次幂,x[i]为系数,inv==1时为DFT,否则iDFT
{
rader(x);
if(inv!=1)inv=-1;
for(int i=1,j,k; i<x.size(); i<<=1)
for(complex<double> u=polar(1.0,acos(-1)/i*inv),w=1,t=j=0;
j<i; ++j,w*=u)
for(k=j; k<x.size(); k+=i<<1)
t=w*x[k+i],x[k+i]=x[k]-t,x[k]+=t;
if(inv==1)return;
for(int i=0; i<x.size(); ++i)x[i]/=x.size();
}
优化高精度乘法
常规算法。
Wint& operator*=(Wint &a,const Wint &b)//fft优化乘法,注意double仅15位有效数字,调小Wint::width不超过2,计算自2*log2(base)+2*log2(len)<53
{
int len=1;
while(len<a.size()||len<b.size())len*=2;
len*=2;
vector<complex<double> > ax(a.begin(),a.end()),bx(b.begin(),b.end());
ax.resize(len),fft(ax,1);
bx.resize(len),fft(bx,1);
for(int i=0; i!=len; ++i)ax[i]*=bx[i];
fft(ax,-1),a.clear();
for(int i=1; i!=len; ++i)
{
unsigned long long n=ax[i-1].real()+0.5;
ax[i]+=n/a.base,a.push_back(n%a.base);
}
a.push_back(ax.back().real()+0.5);
return a.trim(0);
}
快速数论变换(FNTT)
原理和FFT相同,解决特殊情况下FFT的浮点误差,并且可以在同余系进行变换。
对于形如
的费马素数,记其原根为g,则旋转因子为
,满足
且
。
常见素数的原根。
void fntt(vector<ll> &x,ll inv,ll m,ll g)//x.size()为2的整数次幂,x[i]为系数,inv==1时为NTT,否则iNTT;费马素数,原根
{
rader(x);
if(inv!=1)inv=m-2;
for(int i=1,j; i<x.size(); i<<=1)
for(ll u=pow(g,mul(m/2/i,inv,m-1),m),w=1,t=j=0;
j<i; ++j,w=mul(w,u,m))
for(int k=j; k<x.size(); k+=i<<1)
{
t=mul(w,x[k+i],m);
if(x[k+i]=x[k]-t,x[k+i]<0)x[k+i]+=m;
if(x[k]+=t,x[k]>=m)x[k]-=m;
}
if(inv==1)return;
ll u=pow(x.size(),m-2,m);
for(int i=0; i<x.size(); ++i)x[i]=mul(x[i],u,m);
}
优化高精度乘法
常规算法。
Wint& operator*=(Wint &a,const Wint &b)//ntt优化,Wint::width不超过2
{
ll len=1,m=(7<<26)+1,g=3;//费马素数,原根
while(len<a.size()||len<b.size())len*=2;
len*=2;
vector<ll> ax(a.begin(),a.end()),bx(b.begin(),b.end());
ax.resize(len),fntt(ax,1,m,g);
bx.resize(len),fntt(bx,1,m,g);
for(int i=0; i!=len; ++i)ax[i]=mul(ax[i],bx[i],m);
fntt(ax,-1,m,g),a.clear();
for(int i=1; i!=len; ++i)
ax[i]+=ax[i-1]/a.base,a.push_back(ax[i-1]%a.base);
a.push_back(ax.back());
return a.trim(0);
}
快速沃尔什变换(FWT)
再给一个二分的代码便于理解。
void fwt(vector<ll> &x,void f(ll &l,ll &r))
{
for(int i=1; i<x.size(); i<<=1)
for(int j=0; j<i; ++j)
for(int k=j; k<x.size(); k+=i<<1)
f(x[k],x[k+i]);
}
void fwt(ll *b,ll *e,void f(ll &l,ll &r))
{
if(e-b<2)return;
ll *m=b+(e-b)/2;
fwt(b,m,f),fwt(m,e,f);
while(m<e)f(*(b++),*(m++));
}
XOR
可能要在同余系中进行运算,下面代码需要修改。
void tf(ll &l,ll &r)
{
ll tl=l+r,tr=l-r;
l=tl,r=tr;
}
void utf(ll &l,ll &r)
{
tf(l,r),l>>=1,r>>=1;
}
AND
void tf(ll &l,ll &r)
{
l+=r;
}
void utf(ll &l,ll &r)
{
l-=r;
}
OR
void tf(ll &l,ll &r)
{
r+=l;
}
void utf(ll &l,ll &r)
{
r-=l;
}
XNOR,NAND,NOR
直接用异或运算、与运算、或运算的方法求出来,然后将互反的两位交换即可。
Pell方程
形如
(D为任意正整数)的方程称为佩尔方程,必有最小正整数解
,用
可递推方程的第n小整数解(可用矩阵快速幂求),同时还有
Jacobi’s Four Square Theorem
设 的自然数解个数为 r4(n),d(n) 为 n 的约数和,由 Jacobi’s Four Square Theorem 可知,若 n 是奇数,则 r4(n) = 8d(n),否则 r4(n) = 24d(k),k 是 n 去除所有 2 后的结果。
博弈论常用SG函数
一个局面的 SG 为 mex(后继局面的 SG),mex 运算为集合中没出现的最小的自然数。几个 局面的和的 SG 为单个的 SG 异或,SG 不为 0 时先手必胜,SG 为 0 时后手必胜。
Nim Game
n 堆石子,每次可以从一堆里面取任意个石子。对于一堆石子,SG 函数就是石子数。整个 游戏的 SG 函数是每一堆石子的 SG 函数的异或和。 必胜:SG 不为 0,必败:SG 为 0。
Bash Game
每次最多取 m 个石子,其他同 Nim。一堆石子的 SG 函数为石子数 mod(m + 1)。 必胜:SG 不为 0,必败:SG 为 0。
Nim-k Game
每次最多可以同时从 k 堆石子进行操作,这 k 堆可以取不同数量的石子。 一堆石子的 SG 函数为石子数,对每一个二进制位单独算,求 SG 函数每一个二进制位 1 的个数 mod(k + 1),如果都为 0,则必败,否则必胜。
Anti-Nim Game
不能取石子的一方获胜。 必胜:SG 不为 0 且至少有一堆石子数大于 1,SG 为 0 且每一堆石子数都不超过 1 必败:其余为必败。
Anti-SG Game SG
游戏中最先不能行动的一方获胜。 必胜:SG 不为 0 且至少有一个游戏的 SG 大于 1,SG 为 0 且每一个游戏的 SG 都不超过 1 必败:其余为必败。
Staircase Nim
阶梯博弈,每次可以从一个阶梯上拿掉任意数量石子放到下一层阶梯,不能操作的为输。 SG 函数为奇数阶梯上的石子的异或和,如果移动偶数层的石子到奇数层,对手一定可以 继续移动这些石子到偶数层,使得其 SG 不变。 必胜:SG 不为 0,必败:SG 为 0。
Lasker’s Nim Game
n 堆石子,每次可以从一堆里面取任意个石子,或者选择某堆至少为 2 的石子,分成两堆 非空石子。 SG(0) = 0,SG(1) = 1,SG(2) = 2,SG(3) = 4。 对于 k ≥ 1,SG(4k) = 4k−1,SG(4k+1) = 4k+1,SG(4k+2) = 4k+2,SG(4k+3) = 4k+4。
Wythff Game
有两堆石子,每次可以从一堆或者两堆里拿走一样数目的石子,不能取的为输。 必败态为 (1,2)(3,5)(4,7)(6,10)…,差为 1、2、3、4… 每一对数的第一个数为前面没出现的最小的正整数。
递推公式:a[k]=⌊k(1+√5)/2⌋,b[k]=a[k]+k
翻硬币游戏
n 枚硬币排成一排,有的正面朝上,有的反面朝上。游戏者根据某些约束翻硬币(如:每 次只能翻一或两枚,或者每次只能翻连续的几枚),但他所翻动的硬币中,最右边的必须是从正 面翻到反面。谁不能翻谁输。 需要先开动脑筋把游戏转化为其他的取石子游戏之类的,然后用如下定理解决: 局面的 SG 值等于局面中每个正面朝上的棋子单一存在时的 SG 值的异或和。
每一次只能翻转一枚硬币
SG(0) = 0,SG(k) = 1(k > 0)。
每一次可以翻转一枚或两枚硬币
SG(n) = n。
Twins Game
每次必须翻动两个硬币,而且这两个硬币的距离要在可行集 S = 1,2,3 中,相当于 Bash Game。
每一次必须翻连续的 n 个硬币
SG(nk) = 1(k > 0),其他 SG 函数值为 0。
Ruler Game
每一次可以翻任意长度的连续一段硬币,SG(x) 为 x 中包含的 2 的最高次幂,即 SG(x) = ⌊log2 x⌋+ 1
离散数学
归并排序求逆序对
ll merge_sort(ll *b,ll *e)//int存答案可能会爆
{
if(e-b<2)return 0;
ll i=0,j=0,*m=b+(e-b)/2,ans=merge_sort(b,m)+merge_sort(m,e);
vector<ll> l(b,m),r(m,e);
while(i<l.size()&&j<r.size())
{
if(r[j]<l[i])*(b++)=r[j++],ans+=l.size()-i;
else *(b++)=l[i++];
}
while(i<l.size())*(b++)=l[i++];
while(j<r.size())*(b++)=r[j++];
return ans;
}
快速解约瑟夫
ll josephus(ll n,ll k)//编号0~n-1,每k个出列,时间复杂度O(min(n,k))
{
if(n<3)return k%n;
if(n<k)return (Josephus(n-1,k)+k)%n;
ll ret=Josephus(n-n/k,k)-n%k;
return ret<0?ret+n:ret+ret/(k-1);
}
曼哈顿距离的变换
皮克定理
顶点坐标均是整点(或说正方形格点)的简单多边形中,面积S和内部格点数目n、边上格点数目m的满足关系 。
蔡勒公式
w:
对应周日,周一,
,周六
c:世纪减1(即年份前两位数)。
y:年份后两位数。
m:月(
,即在蔡勒公式中,1、2月要看作上一年的13、14 月来计算)。
d:日。
数学分析
增长趋势
积分表
反读可得导数表,此处略。
积分求几何量
面积
若简单闭曲线
端点处连续(
)且其他地方不自交,
都逐段有连续微商,则此闭合曲线围起来的有界区域面积
等式右边称为曲线
上的积分,其计算方法是带入参数方程到定积分计算式中,积分上下限为始点与终点对应的参数值。下限并不总是小于上限,参数从下限到上限变化时对应曲线的正向(沿正向观察时,曲线所围的区域永远在左侧)。
极坐标下,连续非负曲线
与向径
,其中
所围成的平面图形面积
体积
记立体过x点且垂直于x轴的截面面积为 ,则其体积 连续曲线 绕x轴旋转一周产生的旋转体体积
弧长
若简单闭曲线
端点处重合(
)且其他地方不自交,
连续且满足
此时称曲线光滑,其长度
此式可对称推广到高维空间曲线。
极坐标下,
的长度为
曲率
若曲线由参数方程 给出且有二阶微商,则其在一点的曲率 若 ,则 同时记 为曲率半径。
旋转体侧面积
若曲线由参数方程 给出,则其绕x轴旋转体的侧面积
空间曲线的切线与法平面
若已知曲线上一点 处的切向量为 则曲线在该点的切线方程为 法平面方程为 当曲线由参数方程 给出时,曲线在P点的切向量为 更一般地,若曲线用两曲面的交线给出 且在P点的某邻域能确定函数组 满足 ,且 存在,则曲线在P点的切向量
空间曲面的切平面与法线
若已知曲面上一点
处的切平面的法向量为
则曲线在该点的法线方程为
切平面方程为
当曲面方程为
在曲面上任取一条过P的曲线,设其方程为
此时有
令
两边对t求导,并写成向量的内积式,得
则曲线在P点的法向量为
若曲线由参数方程给出
则曲线在P点的法向量
方向导数
设三元函数 在点 的某邻域内有定义,任意给定始于点 的射线 , 为l上且含于定义域内的点。若极限 存在,则称该极限值为函数 在点 沿方向 的方向导数,记为 或 , 称为函数在 点沿 方向的增量。特别地, 就是函数在 点沿 轴正向的方向导数, 轴上的方向导数同理。若函数在 点可微,则其在 沿任何方向 的方向导数都存在,则有以下公式 其中 为 的方向余弦。
泰勒公式
用
表示f(x)的n阶导数。
只要让余项<EPS
即可计算指定函数到任意精确度。
特别取a=0时称为麦克劳林公式。
,佩亚诺余项
,积分余项
,拉格朗日余项
,柯西余项
指数函数
三角函数
对数函数
幂函数
二元函数
设 在 的某邻域 内有直到 阶连续偏导数,则对 内 ,使得 其中
级数部分和
调和级数
幂级数
快速计算幂级数的部分和
可借助伯努利数,详见模板·组合数学。
二分求零点、三分求极值点
需要 在区间 上单调/凹凸性唯一。
double bs(double l,double r,double f(double x))
{
if(r-l<EPS)return l;
double m=(l+r)/2;
return sgn(f(l)*f(m))<0?bs(l,m,f):ts(m,r,f);
}
double ts(double l,double r,double f(double x))
{
if(r-l<EPS)return l;
double d=(r-l)/3,lm=l+d,rm=r-d;
return f(lm)<f(rm)?ts(l,rm,f):ts(lm,r,f);//极小值
}
插值法
拉格朗日插值法:插值多项式和插值基函数的形式对称,容易编程。但是,增加节点时,需要重新计算每一个插值基函数。要在
意义下进行的话,那么p只能是质数。
牛顿插值法:当插值节点增加时,之前已计算的结果仍然能用,每增加一个节点,只要再增加一项即可,从而避免了重复性计算。如果要mod非质数的话,那么就要用牛顿插值法。
typedef complex<double> Coord;
#define X real()
#define Y imag()
double lagrange(const vector<Coord> &p,double x)//返回p确定的多项式函数在x处的值
{
double ret=0;
for(int i=0; i<p.size(); ++i)
{
double tmp=p[i].Y;
for(int j=0; j<p.size(); ++j)
if(i!=j)tmp*=(x-p[j].X)/(p[i].X-p[j].X);
ret+=tmp;
}
return ret;
}
vector<double> lagrange(vector<Coord> p)//返回p确定的多项式系数向量
{
vector<double> ret(p.size()),sum(p.size());
ret[0]=p[0].Y,sum[0]=1;
for(int i=1; i<p.size(); ++i)
{
for(int j=p.size()-1; j>=i; --j)
p[j].Y=(p[j].Y-p[j-1].Y)/(p[j].X-p[j-i].X);
for(int j=i; ~j; --j)
sum[j]=(j?sum[j-1]:0)-sum[j]*p[i-1].X,
ret[j]+=sum[j]*p[i].Y;
}
return ret;
}
double differenceQuotient(const vector<Coord> &p,int k)//计算差商
{
double ret=0;
for(int i=0; i<=k; ++i)
{
double tmp=p[i].Y;
for(int j=0; j<=k; ++j)
if(i!=j)tmp/=p[i].X-p[j].X;
ret+=tmp;
}
return ret;
}
double newton(const vector<Coord> &p,double x)
{
double ret=p[0].Y;
for(int i=1; i<p.size(); ++i)
{
double tmp=differenceQuotient(p,i);//多次求,可O(n^3)预处理优化
for(int j=0; j<i; ++j)tmp*=x-p[j].X;
ret+=tmp;
}
return ret;
}
计算几何
二维
点和向量化为坐标Coord进行运算,使用stl中的complex实现。
复数相乘的几何意义为长度相乘,极角相加。
用直线上的一点p和方向向量v表示一条经过p的直线,直线上的所有点q满足q=p+t*v,其中t是参数;当限制t≥0时,该参数方程表示射线;限制0≤t≤1时,该参数方程表示线段。
此外,如果已知线段端点a1和a2,可以通过Line(a1,a2-a1)来得到对应的参数形式。
Morley定理:三角形每个内角的三等分线相交成等边三角形。
欧拉定理:平面图的点数V、边数E和面数F满足V+F-E=2。
typedef double lf;
typedef complex<lf> Coord;
const lf EPS=1e-9,PI=acos(-1);
#define X real()
#define Y imag()
struct Line
{
Coord p,v;
Line(Coord p=Coord(),Coord v=Coord()):p(p),v(v) {}
Coord point(lf t)
{
return p+v*t;
}
};
struct Circle
{
Coord c;
lf r;
Circle(Coord c=Coord(),lf r=0):c(c),r(r) {}
Coord point(lf t)//t为参数,幅角
{
return c+polar(r,t);
}
};
/*
Coord(lf x=0,lf y=0);//构造函数
lf real(Coord a);//a的实部(复平面的横坐标),也可写作a.real()
lf imag(Coord a);//a的虚部(复平面的纵坐标),也可写作a.imag()
lf abs(Coord a);//向量a的模长,或是点a到原点的距离
lf norm(Coord a);//abs的平方,比abs快,但是要注意浮点数精度溢出
lf arg(Coord a);//a的幅角,与atan2(a.real(),a.imag())等价
Coord polar(lf r,lf t);//极坐标生成方式,r为幅值,t为幅角
//运算符重载+、-、*、/(以及对应的赋值运算,但是赋值运算不能写在表达式中,详见参考地址)、<<、>>(输出括号形式的坐标)
*/
int sgn(lf d)
{
return (d>EPS)-(d<-EPS);
}
bool operator!=(const Coord &A,const Coord &B)//不等运算符,涉及到浮点数比较要重写
{
return sgn(A.X-B.X)||sgn(A.Y-B.Y);
}
bool operator==(const Coord &A,const Coord &B)
{
return !(A!=B);
}
bool cmpCoord(const Coord &A,const Coord &B)//复数没有小于运算,只能这样定义一个比较函数
{
return sgn(A.X-B.X)?
A.X<B.X:
A.Y+EPS<B.Y;
}
bool cmpLine(const Line &A,const Line &B)//按极角排序,求凸包中使用
{
return arg(A.v)<arg(B.v);
}
lf Dot(Coord A,Coord B)
{
return A.X*B.X+A.Y*B.Y;
}
lf Cross(Coord A,Coord B)
{
return A.X*B.Y-B.X*A.Y;
}
lf Angle(Coord A,Coord B)
{
return acos(Dot(A,B)/abs(A)/abs(B));
}
lf Area2(Coord A,Coord B,Coord C)//三角形ABC有向面积的两倍
{
return Cross(B-A,C-A);
}
Coord Rotate(Coord A,lf rad)//向量A逆时针旋转rad弧度
{
return A*polar(1.0,rad);
}
Coord Normal(Coord A)//A的法向量,把A逆时针旋转九十度并长度化为1
{
lf L=abs(A);
return Coord(-A.Y/L,A.X/L);
}
bool onLeft(Coord P,Line L)//p是否在有向直线L左侧,不含线上
{
return Cross(L.v,P-L.p)>0;
}
lf DistanceToLine(Coord P,Line L)//点到直线距离(有向)
{
return Cross(L.v,P-L.p)/abs(L.v);
}
lf DistanceToLine(Coord P,Coord A,Coord B)
{
return DistanceToLine(P,Line(A,B-A));
}
lf DistanceToSegment(Coord P,Coord A,Coord B)//点到线段的距离(无向)
{
if(A==B)return abs(P-A);
Coord v1=B-A,v2=P-A,v3=P-B;
if(sgn(Dot(v1,v2))<0)return abs(v2);
if(sgn(Dot(v1,v3))>0)return abs(v3);
return fabs(DistanceToLine(P,Line(A,B-A)));
}
Coord getLineProjection(Coord P,Line L)//点在直线上的投影
{
return L.point(Dot(L.v,P-L.p)/norm(L.v));
}
Coord getLineProjection(Coord P,Coord A,Coord B)
{
return getLineProjection(P,Line(A,B-A));
}
Coord getSymmetry(Coord P,Coord O)//P关于O的对称点
{
return O+O-P;
}
Coord getSymmetry(Coord P,Line L)//P关于L的对称点
{
return getSymmetry(P,getLineProjection(P,L));
}
Coord getLineIntersection(Line L1,Line L2)//直线交点,须确保两直线相交
{
return L1.point(Cross(L2.v,L1.p-L2.p)/Cross(L1.v,L2.v));
}
Coord getLineIntersection(Coord A1,Coord A2,Coord B1,Coord B2)
{
return getLineIntersection(Line(A1,A2-A1),Line(B1,B2-B1));
}
bool SegmentProperIntersection(Coord A1,Coord A2,Coord B1,Coord B2)//线段相交判定,交点不在一条线段的端点
{
lf C1=Cross(A2-A1,B1-A1),C2=Cross(A2-A1,B2-A1),
C3=Cross(B2-B1,A1-B1),C4=Cross(B2-B1,A2-B1);
return sgn(C1)*sgn(C2)<0&&sgn(C3)*sgn(C4)<0;
}
bool onSegment(Coord P,Coord A1,Coord A2)//判断点是否在线段上,不包含端点
{
return sgn(Dot(A1-P,A2-P))<0&&!sgn(Cross(A1-P,A2-P));
}
lf PolygonArea(const vector<Coord> &p)//计算多边形的有向面积,凸多边形即为面积
{
lf area=0;
for(int i=1,n=p.size()-1; i<n; ++i)
area+=Area2(p[0],p[i],p[i+1]);
return area/2;
}
int inPolygon(Coord p,const vector<Coord> &poly)//点在多边形内的判定,转角法,正值为内部,0为外部,-1在边界上
{
int ans=0;
for(int i=0,k,d1,d2,n=poly.size(); i!=n; ++i)
{
if(onSegment(p,poly[i],poly[(i+1)%n]))return -1;//在边界上
k=sgn(Cross(poly[(i+1)%n]-poly[i],p-poly[i]));
d1=sgn(poly[i].Y-p.Y);
d2=sgn(poly[(i+1)%n].Y-p.Y);
if(k>0&&d1<=0&&d2>0)++ans;
if(k<0&&d2<=0&&d1>0)++ans;
}
return ans;
}
int ConvexHull(vector<Coord> p,vector<Coord> &sol)//获得凸包;不希望凸包的边上有输入点,把两个<=改成<
{
sort(p.begin(),p.end(),cmpCoord);//先比横坐标再比纵坐标
for(int i=0; i!=p.size(); ++i)
{
while(sol.size()>1&&
Area2(sol[sol.size()-2],sol[sol.size()-1],p[i])<=0)
sol.pop_back();
sol.push_back(p[i]);
}
for(int i=sol.size()-2,k=sol.size(); i>=0; --i)
{
while(sol.size()>k&&
Area2(sol[sol.size()-2],sol[sol.size()-1],p[i])<=0)
sol.pop_back();
sol.push_back(p[i]);
}
if(p.size()>1)sol.pop_back();
return sol.size();
}
vector<Coord> cutPolygon(const vector<Coord> &poly,Coord A,Coord B)//用有向直线A->B切割多边形poly, 返回“左侧”。 如果退化,可能会返回一个单点或者线段,复杂度O(n^2)
{
vector<Coord> newpoly;
for(int i=0,n=poly.size(); i!=n; ++i)
{
Coord C=poly[i],D=poly[(i+1)%n];
if(sgn(Cross(B-A,C-A))>=0)newpoly.push_back(C);
if(!sgn(Cross(B-A, C-D)))
{
Coord ip=getLineIntersection(Line(A,B-A),Line(C,D-C));
if(onSegment(ip,C,D))newpoly.push_back(ip);
}
}
return newpoly;
}
vector<Coord> getHalfPlaneIntersection(vector<Line> &L)//半平面交
{
sort(L.begin(),L.end(),cmpLine);//按极角排序
int first,last;//双端队列的第一个元素和最后一个元素
vector<Coord> p(L.size(),Coord()); //p[i]为q[i]和q[i+1]的交点
vector<Line> q(L.size(),Line());//双端队列
q[first=last=0]=L[0]; //队列初始化为只有一个半平面L[0]
for(int i=0,n=L.size(); i!=n; ++i)
{
while(first<last&&!onLeft(p[last-1],L[i]))
--last;
while(first<last&&!onLeft(p[first],L[i]))
++first;
q[++last]=L[i];
if(!sgn(Cross(q[last].v,q[last-1].v)))
{
--last;
if(onLeft(L[i].p,q[last]))
q[last]=L[i];
}
if(first<last)
p[last-1]=getLineIntersection(q[last-1], q[last]);
}
while(first<last&&!onLeft(p[last-1],q[first]))
--last;//删除无用平面
if(last-first<=1)return vector<Coord>();//空集
p[last]=getLineIntersection(q[last],q[first]);
return vector<Coord>(p.begin()+first,p.begin()+last+1);//从deque复制到输出中
}
int getLineCircleIntersection(Line L,Circle C,vector<Coord> &sol)
{
lf a=L.v.X,
b=L.p.X-C.c.X,
c=L.v.Y,
d=L.p.Y-C.c.Y,
e=a*a+c*c,
f=2*(a*b+c*d),
g=b*b+d*d-C.r*C.r,
delta=f*f-4*e*g;
if(sgn(delta)<0)return 0;
if(!sgn(delta))
return sol.push_back(L.point(-f/(2*e))),1;
sol.push_back(L.point((-f-sqrt(delta))/(2*e)));
sol.push_back(L.point((-f+sqrt(delta))/(2*e)));
return 2;
}
int getCircleIntersection(Circle C1,Circle C2,vector<Coord> &sol)
{
lf d=abs(C1.c-C2.c);
if(!sgn(d))
return sgn(C1.r-C2.r)?0:-1;//重合返回-1
if(sgn(C1.r+C2.r-d)<0||sgn(fabs(C1.r-C2.r)-d)>0)//外离或内含
return 0;
lf a=arg(C2.c-C1.c),
da=acos((C1.r*C1.r+d*d-C2.r*C2.r)/(2*C1.r*d));
Coord p1=C1.point(a-da),p2=C1.point(a+da);
sol.push_back(p1);
if(p1==p2)return 1;//相切
return sol.push_back(p2),2;
}
Line getTangent(Coord C,Coord P)//圆心C,圆上一点P处切线
{
return Line(P,Normal(C-P));
}
int getTangents(Coord p,Circle C,vector<Coord> &sol)//点到圆的切点,返回个数
{
Coord u=p-C.c;
lf d=abs(u);
if(d<C.r)return 0;//点在圆内
if(!sgn(d-C.r))//点在圆上
return sol.push_back(p),1;
lf base=arg(u),ang=acos(C.r/d);
sol.push_back(C.point(base+ang));
sol.push_back(C.point(base-ang));
return 2;
}
int getTangents(Circle A,Circle B,vector<Coord> &a,vector<Coord> &b)//公共切线的切点
{
int cnt=0;
if(A.r<B.r)
swap(A,B),swap(a,b);//有时需标记交换
lf d=abs(A.c-B.c),
rdiff=A.r-B.r,
rsum=A.r+B.r;
if(sgn(d-rdiff)<0)return 0;//内含
lf base=arg(B.c-A.c);
if(!sgn(d)&&!sgn(rdiff))return -1;//重合,无穷多条切线
if(!sgn(d-rdiff))//内切,外公切线
{
a.push_back(A.point(base));
b.push_back(B.point(base));
return 1;
}
//有外公切线的情形
lf ang=acos(rdiff/d);
a.push_back(A.point(base+ang));
b.push_back(B.point(base+ang));
a.push_back(A.point(base-ang));
b.push_back(B.point(base-ang));
cnt+=2;
if(!sgn(d-rsum))
{
a.push_back(A.point(base));
b.push_back(B.point(base+PI));
++cnt;
}
else if(sgn(d-rsum)>0)
{
lf ang_in=acos(rsum/d);
a.push_back(A.point(base+ang_in));
b.push_back(B.point(base+ang_in+PI));
a.push_back(A.point(base-ang_in));
b.push_back(B.point(base-ang_in+PI));
cnt+=2;
}
return cnt;
}
lf AreaCircleWithTriangle(Circle C,Coord A,Coord B)//C和三角形OAB的相交面积,如果三角形顶点不在O上则把圆和三角形同时平移,直到有一个顶点在O上
{
int sg=sgn(Cross(A,B));
if(!sg||A==C.c||B==C.c)return 0;
lf OA=abs(A-C.c),OB=abs(B-C.c),angle=Angle(A,B),
d=DistanceToLine(Coord(),A,B);
if(sgn(OA-C.r)<=0&&sgn(OB-C.r)<=0)
return Cross(A,B)/2;
if(sgn(OA-C.r)>=0&&sgn(OB-C.r)>=0&&sgn(d-C.r)>=0)
return sg*C.r*C.r*angle/2;
if(sgn(OA-C.r)>=0&&sgn(OB-C.r)>=0&&sgn(d-C.r)<0)
{
Coord prj=getLineProjection(Coord(),A,B);
if(!onSegment(prj,A,B))return sg*C.r*C.r*angle/2;
vector<Coord> p;
Line L=Line(A,B-A);
getLineCircleIntersection(L,C,p);
lf s1=C.r*C.r*angle/2,
s2=C.r*C.r*Angle(p[0],p[1])/2;
s2-=fabs(Cross(p[0],p[1])/2);
s1=s1-s2;
return sg*s1;
}
if(sgn(OB-C.r)<0)swap(A,B);
Line L=Line(A,B-A);
vector<Coord> inter;
getLineCircleIntersection(L,C,inter);
Coord inter_point=inter[!onSegment(inter[0],A,B)];
lf s=fabs(Cross(inter_point,A)/2);
s+=C.r*C.r*Angle(inter_point,B)/2;
return s*sg;
}
lf AreaCircleWithPolygon(Circle C,const vector<Coord> &p)
{
lf ans=0;
for(int i=0; i<p.size(); ++i)
ans+=AreaCircleWithTriangle(C,p[i],p[(i+1)%p.size()]);
return fabs(ans);
}
Coord getGravityCenter(const vector<Coord> &p)//多边形重心
{
Coord a(0,0);
lf am=0,mj;
for(int i=0; i<p.size(); ++i)
{
mj=Cross(p[i],p[(i+1)%p.size()]);
a+=mj*(p[i]+p[(i+1)%p.size()]);
am+=mj;
}
return a/am/3.0;
}
struct Area//扫描线求各边平行于坐标轴的矩形面积交并
{
struct Seg
{
ll l,r,h;
int f;//矩形上边缘或下边缘
bool operator<(const Seg &s)const
{
return h<s.h;
}
};
vector<Seg> s;
Ranker rk;
void add(ll l,ll b,ll r,ll t)//(l,b)~(r,t)
{
rk.push_back(l),rk.push_back(r);
s.push_back({l,r,b,1}),s.push_back({l,r,t,0});
}
ll ask(int k)//返回重叠层数至少为k的面积
{
ll ans=0,sum=0;
sort(s.begin(),s.end());
rk.init();
vector<int> vis(rk.size(),0);
for(int i=0; i+1<s.size(); ++i)
{
for(int j=rk.ask(s[i].l),e=rk.ask(s[i].r); j<e; ++j)//此处可线段树维护
{
if(s[i].f&&++vis[j]==k)sum+=rk[j+1]-rk[j];
if(!s[i].f&&vis[j]--==k)sum-=rk[j+1]-rk[j];
}
ans+=sum*(s[i+1].h-s[i].h);
}
return ans;
}
};
三维
typedef double lf;
const lf EPS=1e-9,INF=1e9;
struct Coord3
{
lf X,Y,Z;
Coord3(lf X=0,lf Y=0,lf Z=0):X(X),Y(Y),Z(Z) {}
};
int sgn(lf d)
{
return (d>EPS)-(d<-EPS);
}
bool operator!=(const Coord3 &a,const Coord3 &b)
{
return sgn(a.X-b.X)||sgn(a.Y-b.Y)||sgn(a.Z-b.Z);
}
bool operator==(const Coord3 &a,const Coord3 &b)
{
return !(a!=b);
}
Coord3& operator+=(Coord3 &a,const Coord3 &b)
{
return a.X+=b.X,a.Y+=b.Y,a.Z+=b.Z,a;
}
Coord3 operator+(Coord3 a,const Coord3 &b)
{
return a+=b;
}
Coord3& operator-=(Coord3 &a,const Coord3 &b)
{
return a.X-=b.X,a.Y-=b.Y,a.Z-=b.Z,a;
}
Coord3 operator-(Coord3 a,const Coord3 &b)
{
return a-=b;
}
Coord3& operator*=(Coord3 &a,lf d)
{
return a.X*=d,a.Y*=d,a.Z*=d,a;
}
Coord3 operator*(Coord3 a,lf d)
{
return a*=d;
}
Coord3 operator*(lf d,Coord3 a)
{
return a*=d;
}
Coord3& operator/=(Coord3 &a,lf d)
{
return a.X/=d,a.Y/=d,a.Z/=d,a;
}
Coord3 operator/(Coord3 a,lf d)
{
return a/=d;
}
lf Dot(const Coord3& A,const Coord3& B)
{
return A.X*B.X+A.Y*B.Y+A.Z*B.Z;
}
Coord3 Cross(const Coord3& A,const Coord3& B)
{
return Coord3(A.Y*B.Z-A.Z*B.Y,A.Z*B.X-A.X*B.Z,A.X*B.Y-A.Y*B.X);
}
lf norm(const Coord3& A)
{
return Dot(A,A);
}
lf abs(const Coord3& A)
{
return sqrt(norm(A));
}
lf Angle(const Coord3& A,const Coord3& B)
{
return acos(Dot(A,B)/abs(A)/abs(B));
}
lf Area2(Coord3 A,Coord3 B,Coord3 C)
{
return abs(Cross(B-A,C-A));
}
lf Volume6(Coord3 A,Coord3 B,Coord3 C,Coord3 D)//四面体体积
{
return Dot(D-A,Cross(B-A,C-A));
}
Coord3 Centroid(Coord3 A,Coord3 B,Coord3 C,Coord3 D)//四面体的重心
{
return (A+B+C+D)/4.0;
}
lf DistanceToPlane(Coord3 p,Coord3 p0,const Coord3& n)//点p到平面p0-n的有向距离
{
return Dot(p-p0,n)/abs(n);
}
Coord3 getPlaneProjection(Coord3 p,Coord3 p0,const Coord3& n)//点p在平面p0-n上的投影。n必须为单位向量
{
return p-n*Dot(p-p0,n);
}
Coord3 LinePlaneIntersection(Coord3 p1,Coord3 p2,Coord3 p0,Coord3 n)//直线p1-p2 与平面p0-n的交点,假设交点唯一存在
{
Coord3 v=p2-p1;
lf t=Dot(n,p0-p1)/Dot(n,p2-p1);//分母为0,直线与平面平行或在平面上
return p1+v*t;//如果是线段 判断t是否在0~1之间
}
lf DistanceToLine(Coord3 P,Coord3 A,Coord3 B)//点P到直线AB的距离
{
Coord3 v1=B-A,v2=P-A;
return abs(Cross(v1,v2))/abs(v1);
}
lf DistanceToSeg(Coord3 P,Coord3 A,Coord3 B)//点到线段的距离
{
if(A==B)return abs(P-A);
Coord3 v1=B-A,v2=P-A,v3=P-B;
if(sgn(Dot(v1,v2))<0)return abs(v2);
if(sgn(Dot(v1,v3))>0)return abs(v3);
return fabs(DistanceToLine(P,A,B));
}
bool LineDistance3D(Coord3 p1,Coord3 u,Coord3 p2,Coord3 v,lf& s)//求异面直线 p1+s*u与p2+t*v的公垂线对应的s,如果平行|重合,返回0
{
lf b=Dot(u,u)*Dot(v,v)-Dot(u,v)*Dot(u,v);
if(!sgn(b))return 0;
lf a=Dot(u,v)*Dot(v,p1-p2)-Dot(v,v)*Dot(u,p1-p2);
return s=a/b,1;
}
bool SameSide(Coord3 p1,Coord3 p2,Coord3 a,Coord3 b)//p1和p2是否在线段a-b的同侧
{
return sgn(Dot(Cross(b-a,p1-a),Cross(b-a,p2-a)))>=0;
}
bool PointInTri(Coord3 PP,Coord3 P[3])//点P在三角形P0,P1,p中
{
return SameSide(PP,P[0],P[1],P[2])&&
SameSide(PP,P[1],P[0],P[2])&&
SameSide(PP,P[2],P[0],P[1]);
}
bool TriSegIntersection(Coord3 P[3],Coord3 A,Coord3 B,Coord3& PP)//三角形P0P1p是否和线段AB相交,如有则为PP
{
Coord3 n=Cross(P[1]-P[0],P[2]-P[0]);
if(sgn(Dot(n,B-A))==0)return false;//线段A-B和平面P0P1p平行或共面
lf t=Dot(n,P[0]-A)/Dot(n,B-A);//平面A和直线P1-p有惟一交点
if(sgn(t)<0||sgn(t-1)>0)return false;//不在线段AB上
return PointInTri(PP=A+(B-A)*t,P);
}
bool TriTriIntersection(Coord3 T1[3],Coord3 T2[3])//空间两三角形是否相交
{
Coord3 P;
for(int i=0; i<3; ++i)
if(TriSegIntersection(T1,T2[i],T2[(i+1)%3],P)||
TriSegIntersection(T2,T1[i],T1[(i+1)%3],P))
return 1;
return 0;
}
lf SegSegDistance(Coord3 a1,Coord3 b1,Coord3 a2,Coord3 b2,Coord3 &ans1,Coord3 &ans2)//空间两直线上最近点对 返回最近距离 点对保存在ans1 ans2中
{
Coord3 v1=(a1-b1),v2=(a2-b2);
Coord3 N=Cross(v1,v2);
Coord3 ab=(a1-a2);
lf ans=Dot(N,ab)/abs(N);
Coord3 d1=b1-a1,d2=b2-a2,cd=Cross(d1,d2);
lf nd=norm(cd),t1=Dot(Cross(a2-a1,d2),cd)/nd,t2=Dot(Cross(a2-a1,d1),cd)/nd;
return ans1=a1+(b1-a1)*t1,ans2=a2+(b2-a2)*t2,fabs(ans);
}
bool InsideWithMinDistance(Coord3 PP,Coord3 *P,lf dist)//判断PP是否在三角形P中,并且到三条边的距离都至少为dist。保证P,A,B,C共面
{
return PointInTri(PP,P)&&
DistanceToLine(PP,P[0],P[1])>=dist||
DistanceToLine(PP,P[1],P[2])>=dist||
DistanceToLine(PP,P[2],P[0])>=dist;
}
struct ConvexPolyhedron//空间多边形和凸包问题
{
struct Face
{
int v[3];
Face(int a,int b,int c)
{
v[0]=a,v[1]=b,v[2]=c;
}
Coord3 Normal(const vector<Coord3>& P)const
{
return Cross(P[v[1]]-P[v[0]],P[v[2]]-P[v[0]]);
}
bool CanSee(const vector<Coord3>& P,int i)const//f是否能看见P[i]
{
return Dot(P[i]-P[v[0]],Normal(P))> 0;
}
};
vector<Face> faces;
vector<Coord3> p;
ConvexPolyhedron(vector<Coord3> P):p(P)
{
for(int i=0; i<p.size(); ++i)P[i]+=Coord3(randEPS(),randEPS(),randEPS());
vector<vector<int> > vis(P.size(),vector<int>(P.size()));
faces.push_back(Face(0,1,2));//由于已经进行扰动,前三个点不共线
faces.push_back(Face(2,1,0));
for(int i=3; i<P.size(); ++i)
{
vector<Face> next;
for(int j=0; j<faces.size(); ++j)//计算每条边的“左面”的可见性
{
Face& f=faces[j];
int res=f.CanSee(P,i);
if(!res)next.push_back(f);
for(int k=0; k<3; ++k)vis[f.v[k]][f.v[(k+1)%3]]=res;
}
for(int j=0; j<faces.size(); ++j)
for(int k=0; k<3; ++k)
{
int a=faces[j].v[k],b=faces[j].v[(k+1)%3];
if(vis[a][b]!=vis[b][a]&&vis[a][b])//(a,b)是分界线,左边对P[i]可见
next.push_back(Face(a,b,i));
}
swap(faces,next);
}
}
lf randEPS()
{
return (rand()/lf(RAND_MAX)-0.5)*EPS;
}
Coord3 centroid()//三维凸包重心
{
Coord3 C=p[0],tot(0,0,0);
lf totv=0;
for(int i=0; i<faces.size(); ++i)
{
Coord3 p1=p[faces[i].v[0]],p2=p[faces[i].v[1]],p3=p[faces[i].v[2]];
lf v=-Volume6(p1,p2,p3,C);
totv+=v;
tot+=v*Centroid(p1,p2,p3,C);
}
return tot/totv;
}
lf dist(Coord3 C)//凸包内一点到表面最近距离
{
lf ans=INF;
for(int i=0; i<faces.size(); ++i)
{
Coord3 p1=p[faces[i].v[0]],p2=p[faces[i].v[1]],p3=p[faces[i].v[2]];
ans=min(ans,fabs(-Volume6(p1,p2,p3,C)/Area2(p1,p2,p3)));
}
return ans;
}
};
高精度
bitset
代替整型进行位运算,更方便并且可以处理超过最大整形范围大小的位集合。
你可以把bitset看作可以位运算的bool数组,换言之,bitset的大小是固定的。因此,用bitset做状态压缩是很方便的,也可以方便的实现集合的交并补操作。
bitset仅重载了相等不等和位运算符,原生不支持四则运算和大小比较,所以很少代替高精度数。
bitset<N> b(unsigned long long u=0);//用u的低N位初始化b,N是一个可转成ULL类型的常量表达式,高位补0
bitset<N> b(string s,int pos,int m=string::npos,char zero='0',char one='1');//用s从pos位开始的m位初始化b,s中只含zero和one
b.size();//b的大小,即N
b.count();//b中1的个数
b[pos];//b中pos位的引用
b.set();//b全赋1
b.reset();//b全赋0
b.flip();//b全反转
b.to_ull();//b转成unsigned long long,b.size()>64时出错
b.to_string(char zero='0',char one='1');//按参数输出字符串
os<<b;//按'0'和'1'打印b
is>>b;//按'0'和'1'读入b,当下一个字符不是'0'或'1'或读到b.size()个数后停止
==、!=、<<、>>、&、|、^//保持它们在整型运算中的含义
大小比较
其他运算符类似。
typedef bitset<127> Bint;
bool operator<(const Bint &a,const Bint &b)
{
for(int i=a.size()-1; ~i; --i)
if(a[i]<b[i])return 1;
return 0;
}
bool operator!=(const Bint &a,const Bint &b)
{
for(int i=a.size()-1; ~i; --i)
if(a[i]!=b[i])return 1;
return 0;
}
加法
Bint operator+(const Bint &a,const Bint &b)
{
return b.any()?(a^b)+((a&b)<<1):a;
}
Bint& operator+=(Bint &a,const Bint &b)
{
return a=a+b;
}
减法
Bint operator-(const Bint &a)
{
return Bint(1)+~a;
}
Bint& operator-=(Bint &a,const Bint &b)
{
return a+=-b;
}
Bint operator-(Bint a,const Bint &b)
{
return a-=b;
}
乘法
Bint operator*(Bint a,Bint b)
{
Bint r(0);
for(; b.any(); b>>=1,a+=a)if(b[0])r+=a;
return r;
}
Bint& operator*=(Bint &a,const Bint &b)
{
return a=a*b;
}
整除、取模
Bint operator%=(Bint ÷nd,const Bint &divisor)
{
if(dividend<divisor||divisor.none())return dividend;
Bint c,res=0;
for(int k; divisor<dividend;)
{
for(k=0,c=divisor; !(dividend<c); c<<=1,++k)
if(dividend<divisor+c)
{
res+=1<<k;
break;
}
if(dividend<divisor+c)break;
res+=1<<(k-1);
dividend-=c>>1;
}
return dividend;//res是商
}
输入输出
bitset
已经原生重载了输入输出运算符,避免歧义。
istream& getb(istream &is,Bint &val)
{
int sign=1,ch=is.get();
for(; !isdigit(ch)&&ch!=EOF; ch=is.get())
if(ch=='-')
sign=-sign;
for(val=0; isdigit(ch); ch=is.get())
val=(val<<3)+(val<<1)+(ch^'0');
if(sign==-1)val=-val;
return is.putback(ch);
}
ostream& putb(ostream &os,const Bint &val)
{
if(Bint(9)<val)putb(os,val/10);
return os.put(val.to_ulong()%10+'0');
}
高精度无符号整数
构造
预留位宽调整,使用时根据高精度数的位数和高精度乘法的限制调节位宽。
struct Wint:vector<int>//继承vector
{
static const int width=9,base=1e9;
Wint(unsigned long long n=0)//普通初始化,当整型数和Wint同时运算时会提升至Wint
{
for(; n; n/=base)push_back(n%base);
}
explicit Wint(const string &s)//字符串初始化函数,未判断字符串合法情况
{
for(int len=int(s.size()-1)/width+1,b,e,i=0; i!=len; ++i)
for(e=s.size()-i*width,b=max(0,e-width),push_back(0); b!=e; ++b)
back()=back()*10+s[b]-'0';
trim(0);
}
Wint& trim(bool up=1)//去前导0,是否需要进位,很常用的小函数,为方便返回自身
{
for(int i=1; up&&i<size(); ++i)
{
if(at(i-1)<0)--at(i),at(i-1)+=base;
if(at(i-1)>=base)at(i)+=at(i-1)/base,at(i-1)%=base;
}
while(!empty()&&back()<=0)pop_back();
for(; up&&!empty()&&back()>=base; at(size()-2)%=base)
push_back(back()/base);
return *this;
}
};
输入输出
istream& operator>>(istream &is,Wint &n)
{
string s;//懒
return is>>s,n=Wint(s),is;
}
ostream& operator<<(ostream &os,const Wint &n)
{
if(n.empty())return os.put('0');
os<<n.back();
char ch=os.fill('0');
for(int i=n.size()-2; ~i; --i)
os.width(n.width),os<<n[i];
return os.fill(ch),os;
}
大小比较
vector自带大小比较为字典序比较,!=
、==
运算可省,其余运算需要时一定记得重载!
bool operator<(const Wint &a,const Wint &b)
{
if(a.size()!=b.size())return a.size()<b.size();
for(int i=a.size()-1; ~i; --i)
if(a[i]!=b[i])return a[i]<b[i];
return 0;
}
bool operator>(const Wint &a,const Wint &b)
{
return b<a;
}
bool operator<=(const Wint &a,const Wint &b)
{
return !(a>b);
}
bool operator>=(const Wint &a,const Wint &b)
{
return !(a<b);
}
加法和减法
减法,当被减数小于减数时减为0。
Wint& operator+=(Wint &a,const Wint &b)
{
a.resize(max(a.size(),b.size()));//保证有足够的位数
for(int i=0; i!=b.size(); ++i)a[i]+=b[i];
return a.trim();//单独进位防自运算
}
Wint operator+(Wint a,const Wint &b)
{
return a+=b;
}
Wint& operator++(Wint &a)//前置版本
{
return a+=1;//懒
}
Wint operator++(Wint &a,int)//后置版本
{
Wint b=a;
return ++a,b;
}
Wint& operator-=(Wint &a,const Wint &b)//a<b会使a变为0
{
a.resize(max(a.size(),b.size()));//保证有足够的位数
for(int i=0; i!=b.size(); ++i)a[i]-=b[i];
return a.trim();//单独进位防自运算
}
Wint operator-(Wint a,const Wint &b)
{
return a-=b;
}
Wint& operator--(Wint &a)//前置版本
{
return a-=1;//懒
}
Wint operator--(Wint &a,int)//后置版本
{
Wint b=a;
return --a,b;
}
乘法
Wint& operator*=(Wint &a,const Wint &b)
{
Wint c;
c.assign(a.size()+b.size()+2,0);//多开位用于进位
for(int j=0,k,l; j<b.size(); ++j)
if(b[j])//稀疏优化,特殊情况很有效
for(int i=0; i<a.size(); ++i)
{
unsigned long long n=a[i];
for(n*=b[j],k=i+j; n; n/=c.base)
c[k++]+=n%c.base;
for(l=i+j; c[l]>=c.base||l<k; c[l++]%=c.base)
c[l+1]+=c[l]/c.base;
}
return swap(a,c),a.trim(0);
}
Wint operator*(Wint a,const Wint &b)
{
return a*=b;
}
一种效率略高但对位宽有限制的写法
乘法算完后统一进位效率高,防止乘法溢出(unsigned long long范围0~1.8e19),位宽为9时size()不能超过18(十进制162位),位宽为8时size()不能超过1800(十进制14400位)等等。
Wint& operator*=(Wint &a,const Wint &b)
{
vector<unsigned long long> n(a.size()+b.size()+2,0);//防爆int,多开位用于进位
for(int j=0; j!=b.size(); ++j)
if(b[j])//稀疏优化,特殊情况很有效
for(int i=0; i!=a.size(); ++i)
n[i+j]+=(unsigned long long)a[i]*b[j];
for(int i=1; i<n.size(); ++i)//这里用<防止位数0,单独进位防自运算
n[i]+=n[i-1]/a.base,n[i-1]%=a.base;
while(!n.empty()&&!n.back())n.pop_back();
return a.assign(n.begin(),n.end()),a;
}
除法和取模
需要重载<
、+=
、-=
、*=
。
Wint& operator/=(Wint &a,Wint b)
{
Wint r,c,d=b.base/(b.back()+1);
a*=d,b*=d,c.assign(a.size(),0);
for(int i=a.size()-1; ~i; --i)
{
r.insert(r.begin(),a[i]);
unsigned long long s=0;
for(int j=b.size(); j+1>=b.size(); --j)//b.size()==0肯定第一行就出问题的
s=s*b.base+(j<r.size()?r[j]:0);
for(d=c[i]=s/b.back(),d*=b; r<d; r+=b)--c[i];
r-=d;
}
return swap(a,c),a.trim(0);//r为加倍后的余数,可通过高精度除低精度得到真正余数,此处略
}
Wint operator/(Wint a,const Wint &b)
{
return a/=b;
}
Wint& operator%=(Wint &a,const Wint &b)
{
return a-=a/b*b;
}
Wint operator%(Wint a,const Wint &b)
{
return a%=b;
}
开平方
快速解法
51Nod上抄来的代码,Wint::width设置为9的时候能140ms极速A题,设置为1的时候输入101输出11?待研究。
bool cmp(const Wint &a,ll c,int d,const Wint &b)
{
ll l=-(a.base<<1),t=0;
if(b.size()<a.size()+d&&c)return 1;
for(int i=b.size()-1; l<=t&&t<=0&&i>d; --i)
t=t*a.base+c*(i-d-1<a.size()?a[i-d-1]:0)-b[i];
for(int i=d-1; l<=t&&t<=0&&~i; --i)
t=t*a.base-b[i];
return t>0;
}
void sub(Wint &a,const Wint &b,ll k,int d)
{
int l=b.size()+d;
for(int i=d+1; i<=l; ++i)
{
ll tmp=a[i]-k*b[i-d-1];
if(tmp<0)
{
a[i+1]+=(tmp-a.base+1)/a.base;
a[i]=tmp-(tmp-a.base+1)/a.base*a.base;
}
else a[i]=tmp;
}
for(int i=l+1; i<a.size()&&a[i]<0; ++i)
{
a[i+1]+=(a[i]-a.base+1)/a.base;
a[i]-=(a[i]-a.base+1)/a.base*a.base;
}
a.trim(0);
}
Wint sqrt(Wint a)
{
Wint ret;
ret.assign((a.size()+1)>>1,0);
for(int i=ret.size()-1; ~i; --i)
{
int l=0,r=a.base,m=ret[i]=(l+r)>>1;
while(r-l>1)
{
if(cmp(ret,m,i-1,a))r=m;
else l=m;
m=ret[i]=(l+r)>>1;
}
sub(a,ret,m,i-1);
ret[i]+=m;
}
ret.trim(0);
for(int i=0; i<ret.size(); ++i)ret[i]>>=1;
return ret;
}
常规牛顿迭代
只能过51Nod上的5个点。可是好敲啊。
Wint sqrt(const Wint &a)
{
Wint b=a,c=(b+1)/2;
while(b!=c)swap(b,c),c=(b+a/b)/2;
return c;
}
常规按位二分
过五个点。
Wint sqrt(const Wint &a)
{
Wint ret,t;
ret.assign((a.size()+1)>>1,0);
for(int i=ret.size()-1,l,r; ~i; --i)
{
for(l=0,r=a.base; r-l>1;)
{
ret[i]=l+(r-l)/2;
t=ret*ret;
if(a<t)r=ret[i];
else l=ret[i];
}
if(!l&&i==ret.size()-1)ret.pop_back();
else ret[i]=l;
}
return ret;
}
C++语言相关
开栈
c++
#pragma comment(linker,"/STACK:102400000,102400000")
g++
一定要最后写一句exit(0);
退出程序,否则会得到非零退出的错误,可能RE。
int size=256<<20;//256MB
char *p=(char*)malloc(size)+size;
__asm__ __volatile__("movq %0, %%rsp\n"::"r"(p));//64bit
读入优化
仿C++IO流沙雕版
struct Istream
{
char b[20<<20],*i,*e;//20MB
Istream(FILE* in):i(b),e(b+fread(b,sizeof(*b),sizeof(b)-1,in)) {}
bool eof()const
{
return i==e;
}
Istream& operator>>(long long &val)
{
return val=strtoll(i,&i,10/*进制,取值2~36*/),*this;
}
Istream& operator>>(ll &val)//极限快
{
while(*i<'0')++i;//无符号
for(val=0; *i>='0'; ++i)val=(val<<3)+(val<<1)+*i-'0';
return *this;
}
Istream& operator>>(double &val)
{
return val=strtod(i,&i),*this;
}
Istream& operator>>(string &s)
{
while(!eof()&&isspace(*i))++i;
for(s.clear(); !eof()&&!isspace(*i); ++i)s+=*i;
return *this;
}
} kin(stdin);
#define cin kin
C文件指针版
ll getll(FILE* in=stdin)
{
ll val=0,sgn=1,ch=getc(in);
for (; !isdigit(ch)&&ch!=EOF; ch=getc(in))
if(ch=='-')sgn=-sgn;
for (; isdigit(ch); ch=getc(in))
val=val*10+ch-'0';
return ungetc(ch,in),sgn*val;
}
double getd(FILE* in=stdin)
{
double val=getll(in),p=val<0?-1:1;
ll ch=getc(in);
if(ch=='.')
for(ch=getc(in); isdigit(ch); ch=getc(in))
val+=(p/=10)*(ch-'0');
return ungetc(ch,in),val;
}
运算符优先级
算法库
a为容器时直接传迭代器;a为数组,那么beg变成a,end变成a+m,其中m为a的大小(即传地址)。
//用排序sort、去重unique、二分查找lower_bound实现离散化
sort(beg,end);
int n=unique(beg,end)-beg;//离散化后元素个数,vector可直接resize
int k=lower_bound(beg,beg+n,w)-beg;//k为经排序后原来的元素w离散化后对应的值(从0开始)
//以下算法,用于求给定区间的后一\前一字典序排列,不存在返回0,存在返回1
next_permutation(beg,end);
prev_permutation(beg,end);
nth_element(beg,beg+n,end);//将第n大的元素放在第n位,并且比其小的元素都在它前面,比它大的元素都在后面,线性,但不保证有序
max_element(beg,end);//最大值地址
min_element(beg,end);//最小值地址
copy(beg,end,beg2);//指定区间拷贝到beg2开始的地址,需要保证空间足够
fill(beg,end,val);//区间赋值,比memset安全
reverse(beg,end);//区间翻转
容器库/数据结构
struct tm,time_t,mktime
pair
迭代器
顺序容器
c.insert(p,n,t);//在迭代器p指向的位置插入n个值t,原有元素后移;vector、deque很慢
c.insert(p,b,e);//在迭代器p指向的位置插入元素区间[b,e),原有元素后移;[b,e)不能指向c;vector、deque很慢
p=c.erase(p);//删除迭代器p指向的元素,并返回被删元素后面一个元素的迭代器;若p为end,则返回值也为end;vector、deque很慢
p=c.erase(b,e);//删除[b,e)内元素,其余同上
string
string也属于顺序容器,用法(以及后台的实现)与vector<char>
相当。string还附带其他操作:
string s1("c_stri");//用c_string初始化
string s2(s1,pos,n);//用s1从pos位开始的n个初始化s2,n省略时到末尾
s1.substr(pos,n);//s1从pos位开始的n个字符构成的子串,n省略时到末尾
//以下均为查找函数均为O(N*N)的暴力查找而非匹配算法,其中args均为s2,pos,代表从pos开始查找s2,s2亦可为单个字符,pos默认0;不存在返回一个值s.npos
s.find(args);//第一个args
s.rfind(args);//最后一个args,即反向查找
s.find_first_of(args);//s中args任意一个字符第一个出现的位置
s.find_last_of(args);//最后一个
s.find_first_not_of(args);//s中非args中任意一个字符第一个出现的位置
s.find_last_not_of(args);//最后一个
s1+s2;//返回s1尾部缀上s2的结果
s1+=s2;//s1=s1+s2
< > <= >= == !=//比较字典序
关联容器
//初始化,其中T、T1都要是支持<运算符的类型
set<T> s,s1(beg,end);
multiset<T> ms,ms1(beg,end);
map<T1,T2> m;
multimap<T1,T2> mm;
//自定义排序的初始化,以set为例,传一个比较类进去
strcut Cmp
{
bool operater()(const T &t1,const T &t2)
{
return t1>t2;
}
};//这个Cmp其实就是greater<T>
set<T,Cmp> s2;
_s.insert(t);//s或ms中插一个t
_m.insert(make_pair(t1,t2));//m和mm插入需要插入一个pair
p=c.begin();//获得c的首迭代器,指向c最小的元素,效率O(logN);insert操作后失效,需要重新获得
p=c.end();//c的尾迭代器,指向c最大的元素后一位,效率O(logN);insert操作后失效,需要重新获得
//对于map和multimap的迭代器,解引用之后是一个pair
++p;--p;//移动迭代器,效率O(logN),移动到边界后不再移动
c.erase(t);//删除值为t的所有元素,map类的t为pair,返回实际删除元素的数量
q=c.erase(p);//删除迭代器p指向的元素,返回后一个位置的迭代器
q=c.erase(beg,end);//返回[beg,end)返回end
m[t1];//m中下标为t1的索引,若不存在则会执行T2的默认初始化,可能会插入新元素所以m不能为const;否则应使用find代替;mm不支持
c.count(t);//返回c中元素t的数量,不存在为0,非multi容器最多为1
c.find(t);//返回c中第一个值为t的迭代器,不存在返回c.end();
c.lower_bound(k);//指向第一个不小于k元素的迭代器,不存在返回c.end()
c.upper_bound(k);//指向第一个大于k元素的迭代器,不存在返回c.end()
c.equal_range(k);//返回一个迭代器pair表示值为k的元素的范围,不存在均为c.end()
//map和multiset中参数t均表示第一关键字