前言
因为后面几个板子都是跟字符串相关的,笔者就懒得写进“C++竞赛常用实用代码(5)”了,直接放一块,所以这篇的代码量可能是最大的。
简要解释一下为什么我代码中绝大部分结构体命名是“itn”,这是因为早期学写代码时经常把“int”错打为“itn”,所以在写结构体时帮忙区分。
所有代码中未加的默认加上“#define ll long long”
上一页:C++竞赛常用实用代码(3)
目录
下一页:C++竞赛常用实用代码(5)
拓扑排序判环
inline bool check(){
queue<int>q;int t=0;
int dr[n+5];
for(int i=1;i<=n;i++)dr[i]=d[i];//d[i]为第i个点的入度,此处不能破坏原数组,所以copy到dr
for(int i=1;i<=n;i++)if(!dr[i])q.push(i);
while(!q.empty()){
int u=q.front();q.pop(),t++;
for(int i=0;i<G[u].size();i++)
if(dr[G[u][i].a]>0){
int v=G[u][i].a;dr[v]--;
if(!dr[v])q.push(v);
}
}
return t>=n;
}
树链剖分
int n=read(),m,tr[MAXN*3],p,IN;//zkw线段树维护
int d[MAXN],hson[MAXN],tp[MAXN],id[MAXN],fa[MAXN];
vector<int>G[MAXN];
inline int dfs1(int x){
int siz=1,hs=0;d[x]=d[fa[x]]+1;
for(int i=0;i<G[x].size();i++)
if(G[x][i]!=fa[x]){fa[G[x][i]]=x;
int si=dfs1(G[x][i]);siz+=si;
if(si>hs)hs=si,hson[x]=G[x][i];
}
return siz;
}
inline void dfs2(int x){
id[x]=++IN;
if(x==hson[fa[x]])tp[x]=tp[fa[x]];
else tp[x]=x;
if(hson[x]>0)dfs2(hson[x]);
for(int i=0;i<G[x].size();i++)
if(G[x][i]!=fa[x]&&G[x][i]!=hson[x])dfs2(G[x][i]);
}
inline int lca(int u,int v){
while(tp[u]!=tp[v]){
if(d[tp[u]]>d[tp[v]])u=fa[tp[u]];
else v=fa[tp[v]];
}
return d[u]>d[v]?v:u;
}
//其它的都是线段树的板子,不想水了
非旋treap模板
struct node{
int x,y;
node(){}
node(int X,int Y){x=X,y=Y;}
};
inline void updata(int x){siz[x]=siz[ls[x]]+siz[rs[x]]+1;}//维护siz
inline void exc(int x){//下传反转懒标记
if(lazy[x]){lazy[ls[x]]^=1,lazy[rs[x]]^=1,lazy[x]=0,swap(ls[x],rs[x]);}
}
inline node split(int x,int k){//分裂操作
if(!x||!k)return node(0,x);
exc(x),fa[x]=0;node res;
if(siz[ls[x]]>=k)res=split(ls[x],k),ls[x]=res.y,fa[res.y]=x,updata(x),res.y=x;
else res=split(rs[x],k-siz[ls[x]]-1),rs[x]=res.x,fa[res.x]=x,updata(x),res.x=x;
return res;
}
inline int mergg(int x,int y){//合并操作
if(!x||!y)return x^y+(fa[x^y]=0);
exc(x),exc(y);int res;
if(val[x]<val[y])rs[x]=mergg(rs[x],y),fa[rs[x]]=x,updata(x),res=x;
else ls[y]=mergg(x,ls[y]),fa[ls[y]]=y,updata(y),res=y;
return res+(fa[res]=0);
}
//有了分裂和合并,其它操作有手就能打,这里就不用水了
有理数(分数防卡精)结构体
#define ll long long
inline ll gcd(ll a,ll b){return b==0?a:gcd(b,a%b);}
struct Q{
ll z,m;
Q(){m=1;}
Q(ll U,ll V){z=U,m=V;}
Q exc(){//求倒数
Q res;res.z=m,res.m=z;
if(res.m<0)res.z=-res.z,res.m=-res.m;
return res;
}
double dec(){return double(z)/m;}//转换成双精度浮点数
Q operator*(const Q&c){
Q res=c;ll g;
res.z*=z,res.m*=m;
if(res.m<0)res.z=-res.z,res.m=-res.m;
g=max(gcd(res.m,res.z>0?res.z:-res.z),1ll);
res.z/=g,res.m/=g;
return res;
}
Q operator+(const Q&c){
Q res;ll g;
res.m=m*c.m,res.z=z*c.m+m*c.z;
if(res.m<0)res.z=-res.z,res.m=-res.m;
g=max(gcd(res.m,res.z>0?res.z:-res.z),1ll);
res.z/=g,res.m/=g;
return res;
}
};
求树的重心
int ct,ct_,si=0x3f3f3f3f;//ct_是树的另一个重心(如果有的话)
vector<int>G[MAXN];
inline int dfs(int x,int fa){
int mx=0,nu=1;
for(int i=0;i<G[x].size();i++)
if(G[x][i]!=fa){
int v=G[x][i],nm=dfs(v,x);
nu+=nm,mx=max(mx,nm);
}
mx=max(mx,n-nu);
if(mx<si)ct=x,ct_=0,si=mx;
else if(mx==si)ct_=x;
return nu;
}
Prim求最小生成树(多用于完全图)
inline int Prim(){ //邻接矩阵
int an=0; //生成树边的总长
memset(v,0,sizeof(v)),v[1]=1;
for(int i=1;i<=n;i++)w[i]=c[1][i]; //连通块连向其他点的边权
for(int K=1;K<n;K++){
int u=1,ad=0x3f3f3f3f;
for(int i=1;i<=n;i++)if(!v[i]&&w[i]<ad)ad=w[i],u=i;
an+=ad,v[u]=1;
for(int i=1;i<=n;i++)w[i]=min(w[i],c[u][i]);
}
return an;
}
李超树板子(动态开点)
#define INF 0x7f7f7f7f
#define ll long long
int IN;
struct lcs{
int ls,rs;ll k,b;lcs(){}
lcs(ll K,ll B){ls=rs=0,k=K,b=B;}
}t[MAXN<<4];
inline void add(int x,ll l,ll r,ll k,ll b){//插入一条y=kx+b的直线
if(x==0)return;
ll tk=t[x].k,tb=t[x].b,mid=(l+r)>>1;
if(l*k+b>=l*tk+tb&&r*k+b>=r*tk+tb){
t[x].k=k,t[x].b=b;return;
}
else if(l*k+b<l*tk+tb&&r*k+b<r*tk+tb)return;
else{
if(!t[x].ls)t[x].ls=++IN,t[IN]=lcs(tk,tb);
else add(t[x].ls,l,mid,tk,tb);
if(!t[x].rs)t[x].rs=++IN,t[IN]=lcs(tk,tb);
else add(t[x].rs,mid+1,r,tk,tb);
t[x].k=k,t[x].b=b;
}
}
inline ll sch(int x,ll l,ll r,ll g){//查找x=g处的最高点
if(x==0)return -INF;
ll mid=(l+r)>>1,res=g*t[x].k+t[x].b;
if(g<=mid)res=max(res,sch(t[x].ls,l,mid,g));
else res=max(res,sch(t[x].rs,mid+1,r,g));
return res;
}
线段树合并
直接以动态开点李超树为例(相关函数如上)
inline int mergg(int x,int y,ll l,ll r){
if(!x||!y)return x|y;
ll mid=(l+r)>>1;
t[x].ls=mergg(t[x].ls,t[y].ls,l,mid),t[y].ls=0;
t[x].rs=mergg(t[x].rs,t[y].rs,mid+1,r),t[y].rs=0;
add(x,l,r,t[y].k,t[y].b);
return x;
}
快速沃尔什变换FWT
转自讲解:https://blog.csdn.net/weixin_43960414/article/details/106668931
//qm(x,y) 表示 x % y , zxy 是模数
inline void DWTOR(int *s,int m) {//按位或
for(int k = m;k > 1;k >>= 1) {
for(int i = 0;i < m;i += k) {
for(int j = i+(k>>1);j < i+k;j ++) {
int s0 = s[j-(k>>1)],s1 = s[j];
s[j] = qm((s0 +0ll+ s1) , zxy);
}
}
}
return ;
}
inline void IDWTOR(int *s,int m) {//按位或(逆变换)
for(int k = 2;k <= m;k <<= 1) {
for(int i = 0;i < m;i += k) {
for(int j = i+(k>>1);j < i+k;j ++) {
int s0 = s[j-(k>>1)],s1 = s[j];
s[j] = qm((s1 +0ll+ zxy - s0) , zxy);
}
}
}
return ;
}
inline void DWTAND(int *s,int m) {//按位与
for(int k = m;k > 1;k >>= 1) {
for(int i = 0;i < m;i += k) {
for(int j = i+(k>>1);j < i+k;j ++) {
LL s0 = s[j-(k>>1)],s1 = s[j];
s[j-(k>>1)] = qm((s0 +0ll+ s1) , zxy);
}
}
}
return ;
}
inline void IDWTAND(int *s,int m) {//按位与(逆变换)
for(int k = 2;k <= m;k <<= 1) {
for(int i = 0;i < m;i += k) {
for(int j = i+(k>>1);j < i+k;j ++) {
int s0 = s[j-(k>>1)],s1 = s[j];
s[j-(k>>1)] = qm((s0 +0ll+ zxy - s1) , zxy);
}
}
}
return ;
}
inline void DWTXOR(int *s,int m) {//异或
for(int k = m;k > 1;k >>= 1) {
for(int i = 0;i < m;i += k) {
for(int j = i+(k>>1);j < i+k;j ++) {
int s0 = s[j-(k>>1)],s1 = s[j];
s[j] = qm((s0 +0ll+ zxy - s1) , zxy);
s[j-(k>>1)] = qm((s0 +0ll+ s1) , zxy);
}
}
}
return ;
}
inline void IDWTXOR(int *s,int m) {//异或(逆变换)
for(int k = 2;k <= m;k <<= 1) {
for(int i = 0;i < m;i += k) {
for(int j = i+(k>>1);j < i+k;j ++) {
int s0 = s[j-(k>>1)],s1 = s[j];
s[j-(k>>1)] = qm((s0 +0ll+ s1) , zxy) *1ll* inv2 % zxy;
s[j] = qm((s0 +0ll+ zxy - s1) , zxy) *1ll* inv2 % zxy;
}
}
}
return ;
}
KMP板子
for(int i=2,it=0;i<=n;i++){//a为模式串,b为匹配串
while(it>0&&a[it+1]!=a[i])it=next[it];
if(a[it+1]==a[i])next[i]=++it;
}
for(int i=1,it=0;i<=m;i++){
while(it>0&&a[it+1]!=b[i])it=next[it];
if(a[it+1]==b[i])ans=max(ans,++it);//配得长度为it+1
}
EXKMP板子
for(int i=1,l=0,r=0;i<n;i++){//参考预处理吧,懒得打了
if(i<=r)e[i]=min(e[i-l],r-i+1);
while(i+e[i]<n&&s[i+e[i]]==s[e[i]])e[i]++;
if(i+e[i]-1>r)r=i+e[i]-1,l=i;
}
Manacher板子
//s[0]='$',s[1]='#';//s[0]区别处理
//while((s[n+1]=getchar())!='\n'&&s[n+1]>0)s[n+2]='#',n+=2;
for(int i=1,r=0,mid=0;i<=n;i++){
if(i<=r)mnc[i]=min(r-i+1,mnc[(mid<<1)-i]);
while(s[i+mnc[i]]==s[i-mnc[i]])mnc[i]++;
if(i+mnc[i]-1>r)r=i+mnc[i]-1,mid=i;
ans=max(ans,mnc[i]-1);
}
AC自动机板子
struct actrie{ //结构体
int t[26],fail,a;
inline void CL(){ //清空函数
memset(t,0,sizeof(t)),fail=a=0;
}
}t[MAXN];
int IN;
bool v[MAXN]; //vis数组
queue<int>q;
inline void build(){ //读入一个字符串 并 插入至AC自动机
int p=0;char s=getchar();
while((s<'a'||s>'z')&&s>0)s=getchar();
while(s>='a'&&s<='z'){
if(!t[p].t[s-'a'])t[p].t[s-'a']=++IN,t[IN].CL();
p=t[p].t[s-'a'],s=getchar();
}
if(p>0)t[p].a++;
}
inline void getfail(){ //建立fail(必打)
while(!q.empty())q.pop();
for(int i=0;i<26;i++)if(t[0].t[i])t[t[0].t[i]].fail=0,q.push(t[0].t[i]);
while(!q.empty()){
int u=q.front();q.pop();
for(int i=0;i<26;i++){
if(t[u].t[i])t[t[u].t[i]].fail=t[t[u].fail].t[i],q.push(t[u].t[i]);
else t[u].t[i]=t[t[u].fail].t[i];
}
}
}
inline int acfind(bool cl){ //读入一个字符串 并 查询含多少模式串
int p=0,ans=0;char s=getchar();
while((s<'a'||s>'z')&&s>0)s=getchar();
while(s>='a'&&s<='z'){
p=t[p].t[s-'a'];
for(int o=p;o>0&&!v[o];o=t[o].fail)ans+=t[o].a,v[o]=1;
s=getchar();
}
memset(v,0,sizeof(v));
if(cl)IN=0,t[0].CL(); //cl—是否清空AC自动机
return ans;
}
后缀数组sa+height
inline void getsa(){
int q=100; //x[i]为后缀i的第一关键字,y[i]为第二关键字排行为i的后缀
for(int i=1;i<=q;i++)c[i]=0; //c数组为桶
for(int i=1;i<=n;i++)x[i]=s[i]-32,c[x[i]]++;
for(int i=1;i<=q;i++)c[i]+=c[i-1];
for(int i=n;i>0;i--)sa[c[x[i]]--]=i;
for(int N=1;N<=n;N<<=1){
int nm=0;
for(int i=n;i>n-N;i--)y[++nm]=i;
for(int i=1;i<=n;i++)if(sa[i]>N)y[++nm]=sa[i]-N;
for(int i=1;i<=q;i++)c[i]=0;
for(int i=1;i<=n;i++)c[x[i]]++;
for(int i=1;i<=q;i++)c[i]+=c[i-1];
for(int i=n;i>0;i--)sa[c[x[y[i]]]--]=y[i],y[i]=0;
swap(x,y); //把y复制为上一个x数组,x变为空,求新的x数组↓
x[sa[1]]=1,nm=1;
for(int i=2;i<=n;i++)
x[sa[i]]=(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+N]==y[sa[i-1]+N])?nm:++nm;
if(nm==n)break;
q=nm;
}
for(int i=1;i<=n;i++)rk[sa[i]]=i;
}
inline void getheight(){
for(int i=1,k=0;i<=n;i++){
ht[rk[i]]=0;
if(rk[i]==1)continue;
k=max(k-1,0);
int j=sa[rk[i]-1];
while(i+k<=n&&j+k<=n&&s[i+k]==s[j+k])k++;//暴力扩宽
ht[rk[i]]=k;
}
}
后缀自动机
//非广义
struct itn{
int ch[26],len,fa;
itn(){memset(ch,0,sizeof(ch)),len=0;}
}sam[MAXN];
int las=1,tot=1;
inline void samadd(int c){
int p=las,np=las=++tot;sam[np].len=sam[p].len+1;
for(;p&&!sam[p].ch[c];p=sam[p].fa)sam[p].ch[c]=np;
if(!p)sam[np].fa=1;
else{int q=sam[p].ch[c],nq;
if(sam[q].len==sam[p].len+1)sam[np].fa=q;
else{
nq=++tot,sam[nq]=sam[q];
//很多博客说该SAM复杂度是O(n)的,其实不是,它的复杂度为O(n*字符种数),原因就在上面一句和初始化时的memset
//只有把每个点的出边用vector存下来才是O(n),但是不好打
sam[nq].len=sam[p].len+1,sam[q].fa=sam[np].fa=nq;
for(;p&&sam[p].ch[c]==q;p=sam[p].fa)sam[p].ch[c]=nq;
}
}
}
回文自动机
struct itn{
int len,siz,num,ch[30],fail;
}td[MAXN];
int IN=1,las=0;
char s[MAXN];
inline void build(){//初始化,一般打在主函数里
td[1].len=-1,td[0].fail=td[1].fail=1;
}
inline int getf(int x,int n){
while(s[n-td[x].len-1]!=s[n])x=td[x].fail;
return x;
}
inline int extend(int n){//插入操作,返回以第n个字符为结尾的回文串的个数
int cur=getf(las,n),np=td[cur].ch[s[n]-'a'];
if(!np){
np=++IN,td[np].len=td[cur].len+2;
td[np].fail=td[getf(td[cur].fail,n)].ch[s[n]-'a'];
td[cur].ch[s[n]-'a']=np,td[np].num=td[td[np].fail].num+1;
}
td[np].siz++,las=np;
return td[np].num;
}