模板整理

前言

众所周知,一些算法和数据结构是很板子化的,如果我们要用需要先记住他(这不废话吗)
2019.10.8

线段树(区间加与区间求和)

注意右子树区间大小为\(r-(mid+1)+1=r-mid\)

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#define re register
#define int long long
#define maxn 100010
#define ls p<<1
#define rs p<<1|1
#define size (r-l+1)

using namespace std;

inline int read()
{
	int x=0,f=1; char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
int ans;
int n,m,sums[maxn<<2],lazy[maxn<<2],opt,x,y,k,a[maxn];
void push_up(int p)
{
	sums[p]=sums[ls]+sums[rs];
}
void push_down(int p,int l,int r)
{
	int mid=(l+r)>>1;
	lazy[ls]+=lazy[p];
	lazy[rs]+=lazy[p];
	sums[ls]+=(mid-l+1)*lazy[p];
	sums[rs]+=(r-mid)*lazy[p];
	lazy[p]=0;
}
void build(int l,int r,int p)
{
	if(l==r)
	{
		sums[p]=a[l];
		return ;
	}
	int mid=(l+r)>>1;
	build(l,mid,ls);
	build(mid+1,r,rs);
	push_up(p); 
}
void modify(int l,int r,int ql,int qr,int v,int p)
{
	if(ql<=l&&r<=qr)
	{
		sums[p]+=size*v;
		lazy[p]+=v;
		return;
	}
	push_down(p,l,r);
	int mid=(l+r)>>1;
	if(ql<=mid) modify(l,mid,ql,qr,v,ls);
	if(qr>mid) modify(mid+1,r,ql,qr,v,rs);
	push_up(p);
}
void query(int l,int r,int ql,int qr,int p)
{
	if(ql<=l&&r<=qr)
	{
		ans+=sums[p];
		return; 
	}
	push_down(p,l,r);
	int mid=(l+r)>>1;
	if(ql<=mid) query(l,mid,ql,qr,ls);
	if(qr>mid) query(mid+1,r,ql,qr,rs);
}
signed main()
{
//	freopen("1.in","r",stdin);
	n=read(),m=read();
	for(re int i=1;i<=n;++i) a[i]=read();
	build(1,n,1);
	for(re int i=1;i<=m;++i)
	{
		opt=read();
		if(opt==1)
		{
			x=read(),y=read(),k=read();
			modify(1,n,x,y,k,1);
		}
		else
		{
			x=read(),y=read();
			ans=0;
			query(1,n,x,y,1);
			printf("%lld\n",ans);
		}
	}
	return 0;
}

线段树(区间乘、区间加、区间求和)
注意下传标记时先处理乘法标记:

先把子节点加法标记乘要下传的乘法标记,然后子节点乘法标记 乘 乘法标记(乘法比加法运算级优先)乘法标记初始化为1

再处理加法标记,在\(modify2\)函数中也要这样做

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#define re register
#define int long long
#define maxn 100010
#define ls p<<1
#define rs p<<1|1
#define size (r-l+1)

using namespace std;

inline int read()
{
	int x=0,f=1; char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
int ans;
int lazy2[maxn<<2],mod;
int n,m,sums[maxn<<2],lazy[maxn<<2],opt,x,y,k,a[maxn];
void push_up(int p)
{
	sums[p]=sums[ls]+sums[rs];
	sums[p]%=mod;
}
void push_down(int p,int l,int r)
{
     int mid=(l+r)>>1;
     lazy2[ls]=(lazy2[p]*lazy2[ls])%mod;
     lazy2[rs]=(lazy2[p]*lazy2[rs])%mod;
     lazy[ls]=(lazy[ls]*lazy2[p])%mod;
     lazy[rs]=(lazy[rs]*lazy2[p])%mod;
     sums[ls]=(sums[ls]*lazy2[p])%mod;
     sums[rs]=(sums[rs]*lazy2[p])%mod;
     lazy2[p]=1;
     lazy[ls]=(lazy[ls]+lazy[p])%mod;
     lazy[rs]=(lazy[rs]+lazy[p])%mod;
     sums[ls]=(sums[ls]+(mid-l+1)*lazy[p])%mod;
     sums[rs]=(sums[rs]+(r-mid)*lazy[p])%mod;
     lazy[p]=0;
}
void build(int l,int r,int p)
{
	if(l==r)
	{
		lazy2[p]=1;
		sums[p]=a[l];
		return ;
	}
	lazy2[p]=1;
	int mid=(l+r)>>1;
	build(l,mid,ls);
	build(mid+1,r,rs);
	push_up(p); 
}
void modify(int l,int r,int ql,int qr,int v,int p)
{
	if(ql<=l&&r<=qr)
	{
		sums[p]+=size*v;
		sums[p]%=mod;
		lazy[p]+=v;
		lazy[p]%=mod;
		return;
	}
	if((lazy2[p]!=1)||lazy[p]) push_down(p,l,r);
	int mid=(l+r)>>1;
	if(ql<=mid) modify(l,mid,ql,qr,v,ls);
	if(qr>mid) modify(mid+1,r,ql,qr,v,rs);
	push_up(p);
}
void modify2(int l,int r,int ql,int qr,int v,int p)
{
	if(ql<=l&&r<=qr)
	{
		sums[p]*=v;
		sums[p]%=mod;
		lazy[p]*=v;
		lazy[p]%=mod;
		lazy2[p]*=v;
		lazy2[p]%=mod; 
		return;
	}	
    if((lazy2[p]!=1)||lazy[p]) push_down(p,l,r);

	int mid=(l+r)>>1;
	if(ql<=mid) modify2(l,mid,ql,qr,v,ls);
	if(qr>mid) modify2(mid+1,r,ql,qr,v,rs);
	push_up(p);
}
void query(int l,int r,int ql,int qr,int p)
{
	if(ql<=l&&r<=qr)
	{
		ans+=sums[p];
		ans%=mod;
		return; 
	}
    if((lazy2[p]!=1)||lazy[p]) push_down(p,l,r);
	int mid=(l+r)>>1;
	if(ql<=mid) query(l,mid,ql,qr,ls);
	if(qr>mid) query(mid+1,r,ql,qr,rs);
}

signed main()
{
	//freopen("1.in","r",stdin);
	n=read(),m=read(),mod=read();
	for(re int i=1;i<=n;++i) a[i]=read();
	build(1,n,1);
	for(re int i=1;i<=m;++i)
	{
		opt=read();
	//	printf("test:%d\n",sums[9]);
		if(opt==1)
		{
			x=read(),y=read(),k=read();
			modify2(1,n,x,y,k,1);
		}
		else if(opt==2)
		{
			x=read(),y=read(),k=read();
			modify(1,n,x,y,k,1);
		}
		else
		{
			x=read(),y=read();
			ans=0;
			query(1,n,x,y,1);
			printf("%lld\n",ans%mod);
		}
	}
	return 0;
}

卡时,对于某些非计数暴力可以使用这种方法,在程序超时之前输出答案,$TLE$0分,这样做有可能拿到\(10\)

(double)clock()/CLOCKS_PER_SEC>=0.67
具体写法:
if((double)clock()/CLOCKS_PER_SEC>=0.67) exit(0);

2019.10.9

倍增求\(LCA\),注意倍增可以预处理\(lg\)数组进行常数优化

#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cstring>
#define re register
#define ll long long
#define maxn 500100
using namespace std;
inline int read()
{
	int x=0,f=1; char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
struct Edge{
	int v,nxt;
}e[maxn<<2];
int lg[maxn],num;
int n,m,s,cnt,dep[maxn],head[maxn],f[maxn][25],x,y;
inline void add(int u,int v)
{
	e[++cnt].v=v;
	e[cnt].nxt=head[u];
	head[u]=cnt;
}
void dfs(int u,int fa)
{
	dep[u]=dep[fa]+1;
	f[u][0]=fa;
	for(int i=1;(1<<i)<=dep[u];++i)
	f[u][i]=f[f[u][i-1]][i-1];
	for(int i=head[u];i;i=e[i].nxt)
	{
		int v=e[i].v;
	    if(v==fa) continue;
	    dfs(v,u);
	}
}
int lca(int x,int y)
{
	if(dep[y]>dep[x]) swap(x,y);
	for(int i=lg[dep[x]-dep[y]];i>=0;--i)
	{
		if(dep[f[x][i]]<dep[y]) continue;
		x=f[x][i];
	}
	if(x==y) return x;
	for(int i=lg[dep[x]-1];i>=0;--i)
	{
		if(f[x][i]==f[y][i]) continue;
		x=f[x][i],y=f[y][i];
	}
	return f[x][0];
}
int main()
{
	n=read(),m=read(),s=read();
	for(re int i=1;i<n;++i)
	{
		x=read(),y=read();
		add(x,y);
		add(y,x);
	}
	dfs(s,0);
	int tmp=1;
	while(tmp<=n)
	{
		lg[tmp]=num;
		num++;
		tmp*=2;
	}
	for(re int i=1;i<=n;++i)
	{
		if(lg[i]) continue;
		lg[i]=lg[i-1];
	}
	for(re int i=1;i<=m;++i)
	{
		x=read(),y=read();
		printf("%d\n",lca(x,y));
	}
	return 0;
}

\(Dijkstra\)堆优化

#include<cstdio>
#include<iostream>
#include<queue>
#define re register
#define maxn 200010
using namespace std;
inline int read()
{
	int x=0,f=1; char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
struct Edge{
	int v,w,nxt;
}e[maxn<<2];
int a,b,c,s;
int cnt,head[maxn],vis[maxn],n,m,dis[maxn];
inline void add(int u,int v,int w)
{
	e[++cnt].v=v;
	e[cnt].w=w;
	e[cnt].nxt=head[u];
	head[u]=cnt;
}
int ev;
struct node{
	int u,d;
    bool operator <(const node&rhs)const {
    	return rhs.d<d;
	}
};
void dijkstra()
{
	priority_queue<node> q;
	dis[s]=0;
	q.push((node){s,0});
	while(!q.empty())
	{
		node f=q.top();
		q.pop();
		int now=f.u,dd=f.d;
		if(vis[now]) continue;
		vis[now]=1;
		for(int i=head[now];i;i=e[i].nxt)
		{
			ev=e[i].v;
			if(dis[ev]>dis[now]+e[i].w)
			{
				dis[ev]=dis[now]+e[i].w;
				if(!vis[ev])
				{
					q.push((node){ev,dis[ev]});
				}
			}
		} 
	}
	
}
int main()
{
    n=read(),m=read(),s=read();
    for(int i = 1; i <= n; ++i)dis[i] = 0x7fffffff;
    for(re int i=1;i<=m;++i)
    {
        a=read(),b=read(),c=read();
        add(a,b,c);
    }
    dijkstra();
    for(re int i=1;i<=n;++i)
    printf("%d ",dis[i]);
    return 0;
}

\(SPFA\)

#include<cstdio>
#include<iostream>
#include<queue>
#define re register
#define maxn 200010
using namespace std;
inline int read()
{
	int x=0,f=1; char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
struct Edge{
	int v,w,nxt;
}e[maxn<<2];
int a,b,c,s;
int cnt,head[maxn],vis[maxn],n,m,dis[maxn];
inline void add(int u,int v,int w)
{
	e[++cnt].v=v;
	e[cnt].w=w;
	e[cnt].nxt=head[u];
	head[u]=cnt;
}
int ev,eu;
void spfa()
{
	dis[s]=0;
	queue<int> q;
	q.push(s);
	vis[s]=true;
	while(!q.empty())
	{
		eu=q.front();
		q.pop();
		vis[eu]=false;
		for(int i=head[eu];i;i=e[i].nxt)
		{
			ev=e[i].v;
			if(dis[ev]>dis[eu]+e[i].w)
			{
				dis[ev]=dis[eu]+e[i].w;
				if(!vis[ev])
				{
					vis[ev]=true;
					q.push(ev);
				}
			}
		}
	} 
}
int main()
{
    n=read(),m=read(),s=read();
    for(int i = 1; i <= n; ++i)dis[i] = 0x7fffffff;
    for(re int i=1;i<=m;++i)
    {
        a=read(),b=read(),c=read();
        add(a,b,c);
    }
    spfa();
    for(re int i=1;i<=n;++i)
    printf("%d ",dis[i]);
    return 0;
}

双端队列优化\(SPFA\)

#include<cstdio>
#include<iostream>
#include<queue>
#define re register
#define maxn 200010
using namespace std;
inline int read()
{
	int x=0,f=1; char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
struct Edge{
	int v,w,nxt;
}e[maxn<<2];
int a,b,c,s;
int cnt,head[maxn],vis[maxn],n,m,dis[maxn];
inline void add(int u,int v,int w)
{
	e[++cnt].v=v;
	e[cnt].w=w;
	e[cnt].nxt=head[u];
	head[u]=cnt;
}
int ev,eu;
void spfa()
{
	dis[s]=0;
	deque<int> q;
	q.push_back(s);
	vis[s]=true;
	while(!q.empty())
	{
		eu=q.front();
		q.pop_front();
		vis[eu]=false;
		for(int i=head[eu];i;i=e[i].nxt)
		{
			ev=e[i].v;
			if(dis[ev]>dis[eu]+e[i].w)
			{
				dis[ev]=dis[eu]+e[i].w;
				if(!vis[ev])
				{
					vis[ev]=1;
					if(q.empty()||dis[ev]<dis[q.front()]) q.push_front(ev);
					else q.push_back(ev);
				}
			}
		}
	} 
}
int main()
{
    n=read(),m=read(),s=read();
    for(int i = 1; i <= n; ++i)dis[i] = 0x7fffffff;
    for(re int i=1;i<=m;++i)
    {
        a=read(),b=read(),c=read();
        add(a,b,c);
    }
    spfa();
    for(re int i=1;i<=n;++i)
    printf("%d ",dis[i]);
    return 0;
}

堆优化\(SPFA\)

#include<cstdio>
#include<iostream>
#include<queue>
#define re register
#define maxn 200010
using namespace std;
inline int read()
{
	int x=0,f=1; char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
struct Edge{
	int v,w,nxt;
}e[maxn<<2];
int a,b,c,s;
int cnt,head[maxn],vis[maxn],n,m,dis[maxn];
inline void add(int u,int v,int w)
{
	e[++cnt].v=v;
	e[cnt].w=w;
	e[cnt].nxt=head[u];
	head[u]=cnt;
}
struct cmp{
	bool operator ()(int &x,int &y)
	{
		return dis[x]>dis[y];
	}
};
int ev,eu;
void spfa()
{
	dis[s]=0;
	priority_queue<int,vector<int>,cmp> q;
	q.push(s);
	vis[s]=true;
	while(!q.empty())
	{
		eu=q.top();
		q.pop();
		vis[eu]=false;
		for(int i=head[eu];i;i=e[i].nxt)
		{
			ev=e[i].v;
			if(dis[ev]>dis[eu]+e[i].w)
			{
				dis[ev]=dis[eu]+e[i].w;
				if(!vis[ev])
				{
					vis[ev]=1;
					 q.push(ev);
				}
			}
		}
	} 
}
int main()
{
    n=read(),m=read(),s=read();
    for(int i = 1; i <= n; ++i)dis[i] = 0x7fffffff;
    for(re int i=1;i<=m;++i)
    {
        a=read(),b=read(),c=read();
        add(a,b,c);
    }
    spfa();
    for(re int i=1;i<=n;++i)
    printf("%d ",dis[i]);
    return 0;
}

10.10

割点

#include<iostream>
#include<cstdio>
#include<cstring>
#define re register
#define maxn 200010
#define maxm 500020
using namespace std;
int n,m,x,y,cnt,head[maxn],ans;
int flag[maxn],Index,DFN[maxn],LOW[maxn];
struct Edge{
	int v,nxt;
}e[maxm<<2];
inline void add(int u,int v)
{
	e[++cnt].v=v;
	e[cnt].nxt=head[u];
	head[u]=cnt; 
}
void dfs(int x,int fa)
{
	int child=0;
	DFN[x]=LOW[x]=++Index;
	for(int i=head[x];i;i=e[i].nxt)
	{
		int ev=e[i].v;
		if(!DFN[ev])
		{
			dfs(ev,fa);
			LOW[x]=min(LOW[x],LOW[ev]);
			if(LOW[ev]>=DFN[x]&&x!=fa) flag[x]=1;
			
			if(x==fa) child++;
		}
		LOW[x]=min(LOW[x],DFN[ev]);
	}
	if(child>=2&&x==fa) flag[x]=1;
}
int main()
{
	scanf("%d%d",&n,&m);
	for(re int i=1;i<=m;++i)
	{
		scanf("%d%d",&x,&y);
		add(x,y);
		add(y,x);
	}
	for(re int i=1;i<=n;++i)
	 if(!DFN[i]) dfs(i,i);
	for(re int i=1;i<=n;++i)
	{
		if(flag[i]) ans++;
	} 
	printf("%d\n",ans);
	for(re int i=1;i<=n;++i)
	{
		if(flag[i]) printf("%d ",i);
	}
} 

对于语句

LOW[x]=min(LOW[x],DFN[ev])的解释

\(tarjan\)缩点
主要是缩点后对处理后的图进行其他操作

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn =1e5+1e4, maxm=1e5+1e4;
int Index,vis[maxn],num[maxn],low[maxn];
int tot,color[maxn],sum[maxn],f[maxn];
int edge,head[maxn],next[maxm],to[maxm];
int stack[maxn],top; 
int n,m,val[maxn],x[maxm],y[maxm],ans;
inline int read()
{
    int x=0,f=1; char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
void add(int u,int v)
{
      to[++edge]=v; 
      next[edge]=head[u];
      head[u]=edge;
}
void tarjan(int x)
{
	low[x]=num[x]=++Index;
	stack[++top]=x;
	vis[x]=1;
	for(int i=head[x];i;i=next[i])
	{
		int v=to[i];
		if(!num[v])
		{
			tarjan(v);
			low[x]=min(low[x],low[v]);
		}
		else if(vis[v])
		{
			low[x]=min(low[x],low[v]);
		}
	}
	if(low[x]==num[x])
	{
		tot++;
		while(stack[top+1]!=x)
		{
			color[stack[top]]=tot;
			sum[tot]+=val[stack[top]];
			vis[stack[top--]]=0;
		}
	}
}
void search(int x)
{
	if(f[x]) return;
	f[x]=sum[x];
	int maxsum=0;
	for(int i=head[x];i;i=next[i])
	{
		int v=to[i];
		if(!f[v]) search(v);
		maxsum=max(maxsum,f[v]);
	}
	f[x] +=maxsum;
}
int main()
{
     n=read(); m=read();
     for(register int i=1;i<=n;i++) val[i]=read();
     for(register int i=1;i<=m;i++)
     {
           x[i]=read();
           y[i]=read();
           add(x[i],y[i]);
	 }
	 for(register int i=1;i<=n;i++) if(!num[i]) tarjan(i);
	 memset(head,0,sizeof(head));
	 memset(next,0,sizeof(next));
	 memset(to,0,sizeof(to));
	 edge=0;
	 for(register int i=1;i<=m;i++)
	 {
	    if(color[x[i]]!=color[y[i]])
	      add(color[x[i]],color[y[i]]);
	 }
	 for(register int i=1;i<=tot;i++)
	 {
	     if(!f[i])
	     {
	        search(i);
	        ans=max(ans,f[i]);
		 }
	 }
	 printf("%d",ans);
	 return 0;
}

树链剖分

树链剖分是方法,配合线段树等才是算法

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#define re register
#define maxn 200010
using namespace std;
inline int read()
{
	int x=0,f=1; char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}

int id[maxn],res;
int son[maxn],maxson,ans,sums[maxn<<2],lazy[maxn<<2];
int w[maxn],wt[maxn],cnt,top[maxn],fa[maxn],dep[maxn];
int mod,n,m,root,opt,x,y,z,head[maxn],cnts,siz[maxn];
struct Edge{
	int v,nxt;
}e[maxn<<2];
inline void add(int u,int v)
{
	e[++cnts].v=v;
	e[cnts].nxt=head[u];
	head[u]=cnts;
}
//-------------线段树---------------
#define ls p<<1
#define rs p<<1|1
#define size (r-l+1)
void push_down(int p,int l,int r)
{
	lazy[p]%=mod;
	lazy[ls]=(lazy[p]+lazy[ls])%mod;
	lazy[rs]=(lazy[p]+lazy[rs])%mod;
    int mid=(l+r)>>1;
    sums[ls]=(sums[ls]+lazy[p]*(mid-l+1))%mod;
    sums[rs]=(sums[rs]+lazy[p]*(r-mid))%mod;
    lazy[p]=0;
}
void push_up(int p){sums[p]=(sums[ls]+sums[rs])%mod;}
void build(int l,int r,int p)
{
	if(l==r)
	{
		sums[p]=wt[l];
		return;
	}
	int mid=(l+r)>>1;
	build(l,mid,ls);
	build(mid+1,r,rs);
	push_up(p);
} 

void update(int p,int l,int r,int ql,int qr,int k)
{
	if(ql<=l&&r<=qr) 
	{
		sums[p]=(sums[p]+k*size)%mod;
		lazy[p]=(lazy[p]+k)%mod;
		return;
	}
	push_down(p,l,r);
	int mid=(l+r)>>1;
	if(ql<=mid) update(ls,l,mid,ql,qr,k);
	if(qr>mid) update(rs,mid+1,r,ql,qr,k);
	push_up(p);
}
void query(int p,int l,int r,int ql,int qr)
{
	if(ql<=l&&r<=qr)
    {
    	res=(sums[p]+res)%mod;
    	return;
	}
	push_down(p,l,r);
	int mid=(l+r)>>1;
	if(ql<=mid) query(ls,l,mid,ql,qr);
	if(qr>mid) query(rs,mid+1,r,ql,qr);
}
//-------------线段树--------------- 
void dfs1(int u,int fat,int deep)
{
	dep[u]=deep;
	fa[u]=fat;
	siz[u]=1;
	maxson=-1;
	for(int i=head[u];i;i=e[i].nxt)
	{
		int ev=e[i].v;
		if(ev==fat) continue;
		dfs1(ev,u,deep+1);
		siz[u]+=siz[ev];
		if(siz[ev]>maxson) maxson=siz[ev],son[u]=ev;
	}
}
void dfs2(int u,int topf)
{
	id[u]=++cnt;
	wt[cnt]=w[u];
	top[u]=topf;
	if(!son[u]) return;
	dfs2(son[u],topf);
	for(int i=head[u];i;i=e[i].nxt)
	{
		int ev=e[i].v;
		if(ev==fa[u]) continue;
		if(son[u]==ev) continue;
		dfs2(ev,ev);
	}
}
void addRange(int x,int y,int z)
{
	z%=mod;//这里别忘取模 
	while(top[x]!=top[y])//不在一条链上,别写成top[y]!=x 
	{
		if(dep[top[x]]<dep[top[y]]) swap(x,y);//注意让x的顶端深度而不是x的深度是两者中最大的
		update(1,1,n,id[top[x]],id[x],z);
		x=fa[top[x]];
	}
	if(dep[y]>dep[x]) swap(x,y);
	update(1,1,n,id[y],id[x],z);
	
}
void addSon(int x,int z)
{
	z%=mod;
	update(1,1,n,id[x],id[x]+siz[x]-1,z);
}
void qSon(int x)
{
	res=0;
	query(1,1,n,id[x],id[x]+siz[x]-1);
	ans=(ans+res)%mod;
}
void qRange(int x,int y)
{
	while(top[x]!=top[y])
	{
		if(dep[top[x]]<dep[top[y]]) swap(x,y);
		res=0;
		query(1,1,n,id[top[x]],id[x]);
		ans=(ans+res)%mod;
		x=fa[top[x]];
	}
	if(dep[y]>dep[x]) swap(x,y);
	res=0;
	query(1,1,n,id[y],id[x]);
	ans=(ans+res)%mod;
}
int main()
{
	n=read(),m=read(),root=read(),mod=read();
	for(re int i=1;i<=n;++i) w[i]=read();
	for(re int i=1;i<n;++i)
	{
		x=read(),y=read();
		add(x,y);
		add(y,x);
	}
	dfs1(root,0,1);
	dfs2(root,root);
	build(1,n,1);
	for(re int i=1;i<=m;++i)
	{
		opt=read();
		if(opt==1)
		{
			x=read(),y=read(),z=read();
			addRange(x,y,z);
		}
		else if(opt==2)
		{
			x=read(),y=read();
			ans=0;
			qRange(x,y);
			printf("%d\n",ans);
		}
		else if(opt==3)
		{
			x=read(),z=read();
			addSon(x,z);
		}
		else
		{
			x=read();
			ans=0;
			qSon(x);
			printf("%d\n",ans);
		}
	}
	return 0;
}

树状数组1(单点修改,区间查询)

#include<cstdio>
#include<iostream>
#include<queue>
#define maxn 2000010
#define lowbit(x) x&(-x)
//#define int long long
#define re register
using namespace std;
inline int read()
{
	int x=0,f=1; char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
int s[maxn<<1];
int n,m,x,y,k,opt;
inline void add(int x,int k)
{
	while(x<=n)
	{
		s[x]+=k;
		x+=lowbit(x);
	}
}
inline int query(int x)
{
	int ans=0;
	while(x>0)
	{
		ans+=s[x];
		x-=lowbit(x);
	}
	return ans;
}
int main()
{
	n=read(),m=read();
	for(re int i=1;i<=n;++i)
	{
		x=read();
		add(i,x);
	}
	for(re int i=1;i<=m;++i)
	{
		opt=read(),x=read(),k=read();
		if(opt==1)	add(x,k);
		else  printf("%d\n",query(k)-query(x-1));
	}
	return 0;
}

树状数组2(区间修改单点查询)

思想:树状数组维护的是前缀和,因此直接加入差分数组求前缀和就是原数组

#include<cstdio>
#include<iostream>
#include<queue>
#define int long long
#define maxn 2000010
#define lowbit(x) x&(-x)
//#define int long long
#define re register
using namespace std;
inline int read()
{
	int x=0,f=1; char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
int s[maxn<<1];
int n,m,x,y,k,opt;
inline void add(int x,int k)
{
	while(x<=n)
	{
		s[x]+=k;
		x+=lowbit(x);
	}
}
inline int query(int x)
{
	int ans=0;
	while(x>0)
	{
		ans+=s[x];
		x-=lowbit(x);
	}
	return ans;
}
int last;
signed main()
{
	n=read(),m=read();
	for(re int i=1;i<=n;++i)
	{
		x=read();
		add(i,x-last);//巨坑,注意加入的数是差分数组
		last=x;
	}
	for(re int i=1;i<=m;++i)
	{
		opt=read();
		if(opt==1)
		{
			x=read(),y=read(),k=read();
			add(x,k);
			add(y+1,-k);
		}	
		else 
		{
			x=read();
			printf("%lld\n",query(x));
		}
	}
	return 0;
}

10.12

最小生成树\(prim\)

基于\(dijkstra\)的思想,稠密图更优

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#define INF 0x3f3f3f3f
#define re register
#define maxn 50010
#define maxm 200010 
using namespace std;
inline int read()
{
	int x=0,f=1; char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
int n,m,ans,minn,now,vis[maxn],head[maxn],x,y,z,cnt,tot;
int dis[maxn],ev;
struct Edge{
	int v,w,nxt;
}e[maxm<<2];//坑,数组开边的大小的两倍不是点的大小 
inline void add(int u,int v,int w)
{
	e[++cnt].v=v;
	e[cnt].w=w;
	e[cnt].nxt=head[u];
	head[u]=cnt;
}
inline int prim()
{
	memset(dis,INF,sizeof(dis));
	dis[1]=0;
	for(re int i=head[1];i;i=e[i].nxt)
	{
		ev=e[i].v;
		dis[ev]=min(dis[ev],e[i].w);
	}
	now=1;
	while(++tot<n)
	{
		minn=INF;
		vis[now]=1;
		for(re int i=1;i<=n;++i)
		{
			if(!vis[i]&&minn>dis[i])
			{
				minn=dis[i];
				now=i;
			}
		}
		ans+=minn;
		for(re int i=head[now];i;i=e[i].nxt)
		{
			ev=e[i].v;//注意这里找的不是到源点的最短路而是到已经访问边集的最短路
			//dis表示的是到当前边集,因此dis[ev]>e[i].w而不是
			//dis[ev]>dis[now]+e[i].w 
			if(dis[ev]>e[i].w&&!vis[ev])
			{
				dis[ev]=e[i].w;
			}
		}
	}
	return ans;
}
int main()
{
	n=read(),m=read();
	for(re int i=1;i<=m;++i)
	{
		x=read(),y=read(),z=read();
		add(x,y,z);
		add(y,x,z);
	}
	printf("%d\n",prim());
	return 0;
}

最小生成树\(kruskal\)(附带按秩合并、路径压缩模板)

写起来更简单,稀疏图快

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#define INF 0x3f3f3f3f
#define re register
#define maxn 5050
#define maxm 200010
using namespace std;
inline int read()
{
	int x=0,f=1; char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
struct Edge{
	int u,v,w;
}e[maxm<<2];
int ev,eu,ans,fa[maxn],n,m,cnt,ranks[maxn];
int find(int x)
{
	return fa[x]==x?x:fa[x]=find(fa[x]);
}
void merge(int x,int y)
{
	//c++11中rank是函数 
	//ranks[i]表示以i为根的树的树高 
	//注意只有两个树rank相等时总ranks才++ 
	if(ranks[y]>ranks[x])
	{
		fa[x]=y;
	}
	else
	{
		fa[y]=x;
		if(x==y) ranks[x]++;
	}
}
void pre()
{
	for(re int i=1;i<=n;++i) fa[i]=i;
	ans=0;
}
bool cmp(Edge A,Edge B)
{
	return A.w<B.w;
}

inline int kruskal()
{
	pre();
	sort(e+1,e+m+1,cmp);
	for(re int i=1;i<=m;++i)
	{
		eu=find(e[i].u),ev=find(e[i].v);
		if(eu==ev) continue;
		merge(ev,eu);//将父亲合并 
		ans+=e[i].w;
		if(++cnt==n-1) break;
	}
	return ans;
}
int main()
{
	n=read(),m=read();
	for(re int i=1;i<=m;++i)
	{
		e[i].u=read(),e[i].v=read(),e[i].w=read();
	}
	printf("%d\n",kruskal());
	return 0;
}

大随机数生成

\(c++11\),开完一定记得关上,避免使用\(c++11\)的东西导致\(CE\)

命令:\(-std=c++11\)

#include<random>//注意
#include<ctime>
using namespace std;
mt19937 rnd(time(0));
int main()
{
    printf("%lld\n",rnd());
    return 0;
}

区间查询区间修改树状数组

比线段树常数小,注意一开始加入的数是差分数组

void add(ll p, ll x){
    for(int i = p; i <= n; i += i & -i)
        sum1[i] += x, sum2[i] += x * p;
}
void range_add(ll l, ll r, ll x){
    add(l, x), add(r + 1, -x);
}
ll ask(ll p){
    ll res = 0;
    for(int i = p; i; i -= i & -i)
        res += (p + 1) * sum1[i] - sum2[i];
    return res;
}
ll range_ask(ll l, ll r){
    return ask(r) - ask(l - 1);
}

二维树状数组

相当于多加一维,先把\(y\)当做一维考虑,求和或者修改,在用同样的方法在\(x\)轴上扩展(相当于把\(y\)轴的信息压缩成一维再搞)。

求和需要用到二维前缀和,容斥即可


单点修改区间查询

#include<cstdio>
#include<iostream>
#define re register
#define maxn 1010
using namespace std;
int opt;
int tree[maxn][maxn],n,x,y,k,a,b,c,d,m;
void add(int x,int y,int z)
{
	int memo_y=y;
	while(x<=n)
	{
		y=memo_y;
		while(y<=n)
		  tree[x][y]+=z,y+=(y&-y);
		x+=(x&-x);
	}
}
int ask(int x,int y)
{
	int res=0,memo_y=y;
	while(x)
	{
		y=memo_y;
		while(y)
		 res+=tree[x][y],y-=(y&-y);
		x-=(x&-x);
	}
	return res;
}
int query(int a,int b,int c,int d)//询问左上角(a,b),右下角(c,d)
{
	return ask(c,d)-ask(c,b-1)-ask(a-1,d)+ask(a-1,b-1);
}
int main()
{
	scanf("%d%d",&n,&m);
	for(re int i=1;i<=m;++i)
	{
		scanf("%d",&opt);
		if(opt==1)
		{
			scanf("%d%d%d",&x,&y,&k);
			add(x,y,k);
		}
		else
		{
			scanf("%d%d%d%d",&a,&b,&c,&d);
			printf("%d\n",query(a,b,c,d));
		}
	}
	return 0;
}

区间修改单点查询

同样道理,对差分数组求前缀和

二维前缀和的公式是这样的:

sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+a[i][j];

又因为普通数组是对差分数组求前缀和
可以推出

    a[i][j]=a[i-1][j]+a[i][j-1]-a[i-1][j-1]+d[i][j];
    d[i][j]=a[i][j]+a[i-1][j-1]-a[i-1][j]-a[i][j-1];

假设我们要加入的数组是这样的

    0 0 0 0 0
    0 0 0 0 0
    0 1 1 1 0
    0 1 1 1 0
    0 1 1 1 0
    0 0 0 0 0

那么显然差分数组这样加入

    0 0 0 0 0
    0 0 0 0 0
    0 1 0 0 -1 
    0 0 0 0 0
    0 0 0 0 0
    0 -1 0 0 1

其实感性理解一下就是两个方向分别作一维差分
终极版:二维区间查询+区间修改树状数组

#include<cstdio>
#include<iostream>
#define re register
#define maxn 1001
using namespace std;
inline int read()
{
	int x=0,f=1; char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
int a[maxn][maxn];
int n,m,opt,xa,ya,xb,yb,x,y,z;
int t1[maxn][maxn],t2[maxn][maxn],t3[maxn][maxn],t4[maxn][maxn] ;
void add(int x,int y,int z)
{
	for(int i=x;i<=n;i+=(i&-i))
	 for(int j=y;j<=n;j+=(j&-j))
	 {
	 	t1[i][j]+=z;
	 	t2[i][j]+=z*x;
	 	t3[i][j]+=z*y;
	 	t4[i][j]+=z*x*y;
	 }
}
void range_add(int xa,int ya,int xb,int yb,int z)
{
	add(xa,ya,z);
	add(xa,yb+1,-z);
	add(xb+1,ya,-z);
	add(ya+1,yb+1,z);
}
int ask(int x,int y)
{
	int res=0;
	for(int i=x;i;i-=(i&-i))
	 for(int j=y;j;j-=(j&-j))
	{
		res+=((x+1)*(y+1)*t1[i][j]-(y+1)*t2[i][j]-(x+1)*t3[i][j]+t4[i][j]); 
	}
	return res;
} 
int range_ask(int xa,int ya,int xb,int yb)
{
	return ask(xb,yb)-ask(xb,ya-1)-ask(xa-1,yb)+ask(xa-1,ya-1);
}
//二维树状数组区间查询需要用到前缀和
int main()
{
	n=read(),m=read();
	for(re int i=1;i<=n;++i)
	 for(re int j=1;j<=n;++j)
	 {
	 	a[i][j]=read();
	 	add(i,j,a[i][j]+a[i-1][j-1]-a[i][j-1]-a[i-1][j]);
	 	//注意加入二维差分数组,具体怎么算出来的前面推导过 
	 } 
	for(re int i=1;i<=m;++i)
	{
		opt=read();
		if(opt==1)
		{
			xa=read(),ya=read(),xb=read(),yb=read(),z=read();
			range_add(xa,ya,xb,yb,z);
		}
		else
		{
			xa=read(),ya=read(),xb=read(),yb=read();
			printf("%d\n",range_ask(xa,ya,xb,yb));
		}
	}
	return 0;
}

对于树状数组小总结

最基本的树状数组其实只是单点修改和区间查询

其实差分相当于降一次,前缀和相当于升一次

通过差分操作可以实现区间修改和单点查询

对差分数组求两次前缀和就可以实现区间修改区间查询了

对于二维树状数组实际上是把它的信息压成一维再进行一维树状数组

经常会出现\(xy\sum\limits_{i=1}^x\sum\limits_{j=1}^yd[i][j]\)这样的公式,有多少项一般就要维护多少个树状数组,比如一维是

\((p+1)\sum\limits_{i=1}^pd[i]-\sum\limits_{i=1}^pd[i]\cdot i\)

需要维护两个树状数组

二维是

\((x+1)(y+1)\sum\limits_{i=1}^x\sum\limits_{j=1}^yd[i][j]-(y+1)\sum\limits_{i=1}^x\sum\limits_{j=1}^yd[i][j]\cdot i-(x+1)\sum\limits_{i=1}^x\sum\limits_{j=1}^yd[i][j]\cdot j+\sum\limits_{i=1}^x\sum\limits_{j=1}^yd[i][j]\cdot ij\)

对于这种式子,将变量放到前面,变量不需要参与到树状数组的插入,因为这跟询问区间有关,只需要将变量插入即可,考虑每一个\(d[i][j]ij\)

(区间修改实际也是单点插入差分数组),它真正修改的是一个点,所以\(d[i][j]ij\)是一个定值,写成\(d[x][y]xy\),只是向上修改每一个影响区间的时候都加上这个定值即可,所以树状数组插入查询的都是定值


2019.10.15

权值线段树(+离散化模板)

普通线段树当前区间\(l,r\)表示从\(a[l]\)\(a[r]\)

权值线段树当前区间\(l,r\)表示从权值\(l\)\(r\),线段树的结构有两种建造方法,一种是和普通线段树一样\(ls=p<<1\),一种是根据\(dfs\)的顺序设置标号

权值线段树一般需要离散化,分为\(3\)

\(1.sort\)排序,一定要排序

\(2.unique\)返回除去重复元素后的数组大小

\(3.lower_bound\)让每个\(a[i]\)在排序后的\(b\)数组中找自己,相当于是返回自己的排名,排名显然\(<=n\)这也就达成了离散化不会爆空间的目的,把排名插进去(因为只需要维护这\(n\)个数的大小关系,所以插排名就行),最后\(query\)找到的排名\(ans\)直接输出\(b[ans]\)就是原答案

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#define re register
#define maxn 100010
using namespace std; 
inline int read()
{
	int x=0,f=1; char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
#define ls p<<1
#define rs p<<1|1
int tmp,n,a[maxn],cnt[maxn<<2],ans,b[maxn],len;
void push_up(int p) {cnt[p]=cnt[ls]+cnt[rs];}
void insert(int p,int l,int r,int v)
{
	if(l==r) 
	{
		 cnt[p]++;//这里是打在树的节点上的标记
		 //别写成cnt[l]++; 
		return;
	}
	int mid=(l+r)>>1;
	if(v<=mid) insert(ls,l,mid,v);
	else insert(rs,mid+1,r,v);
	push_up(p);
}
void query(int p,int l,int r,int qrank)
{
	if(l==r)
	{
		ans=l;
		return;
	}
	int mid=(l+r)>>1;
	if(cnt[ls]>=qrank) query(ls,l,mid,qrank);
	else query(rs,mid+1,r,qrank-cnt[ls]);
}
int main()
{
	n=read();
	for(re int i=1;i<=n;++i) a[i]=read(),b[i]=a[i];
	sort(b+1,b+n+1);
	len=unique(b+1,b+n+1)-b-1;
	for(re int i=1;i<=n;++i)
	{
		tmp=lower_bound(b+1,b+len+1,a[i])-b;//找到它的排名 
		insert(1,1,len,tmp);//线段树长度离散化后只有len 
		if(i%2) 
		{
			ans=0;
		   query(1,1,len,(1+i)/2);
		   printf("%d\n",b[ans]);//找到该排名对应的值 
		}
	}
}

哈希

一维哈希

	for(re int i=1;i<=n;++i)
	a[i]=a[i-1]*base+s[i];
	//对于i-j的串
	ans=a[j]-a[i-1]*fac[j-i]; 

二维哈希

	for(re int i=1;i<=n;++i)
	 for(re int j=1;j<=m;++j)//注意行列使用不同的base 
	  a[i][j]=a[i][j-1]*base1+b[i][j];//这里要加b[i][j] 
	for(re int i=1;i<=n;++i)
	 for(re int j=1;j<=m;++j)
	  a[i][j]+=a[i-1][j]*base2;//这里不加 
	//对于(x1,y1)-(x2,y2)的矩阵
	//fac[n]表示base的n次方 
	ans=a[x2][y2]-a[x2][y1-1]*fac1[y2-y1]-a[x1-1][y2]*fac2[x2-x1]+a[x1-1][y1-1]*fac1[y2-y1]*fac2[x2-x1]; 

10.21

主席树(可持久化线段树)

静态区间第\(k\)

主席树实际上是线段树的前缀和,每个点建一条链,做前缀和即可,注意注意数组开\(16\)倍以上,正常线段树\(4\)倍,但是这个需要多余开点。

重点看看\(modify\)函数

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#define re register
#define maxn 500010
using namespace std;
inline int read()
{
	int x=0,f=1; char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
int n,m,l,r,k,cnt,ans,tmp,len,sums[maxn<<3];
int a[maxn],b[maxn],rt[maxn<<2],lc[maxn<<3],rc[maxn<<3];//开大 
void build(int &h,int l,int r)
{
	h=++cnt;//按照dfs序建点
	if(l==r) return;
	int mid=(l+r)>>1;
	build(lc[h],l,mid);
	build(rc[h],mid+1,r);
} 
int modify(int o,int l,int r)
{
	int h=++cnt;
    lc[h]=lc[o],rc[h]=rc[o],sums[h]=sums[o]+1;
	if(l==r) return h;
	int mid=(l+r)>>1;
	if(tmp<=mid) lc[h]=modify(lc[h],l,mid);
	else rc[h]=modify(rc[h],mid+1,r);
	return h;
}
void query(int u,int v,int l,int r,int k)
{
	if(l==r)
	{
		ans=l;
		return;
	} 
	int num=sums[lc[v]]-sums[lc[u]];
	int mid=(l+r)>>1;
	if(k<=num) query(lc[u],lc[v],l,mid,k);
	else query(rc[u],rc[v],mid+1,r,k-num);
}
int main()
{
	n=read(),m=read();
	for(re int i=1;i<=n;++i) a[i]=read(),b[i]=a[i];
	sort(b+1,b+n+1);	
	len=unique(b+1,b+n+1)-b-1;
	build(rt[0],1,len);
	for(re int i=1;i<=n;++i)
	{
		tmp=lower_bound(b+1,b+len+1,a[i])-b;//注意这里是b+len+1而不是b+n+1 
		rt[i]=modify(rt[i-1],1,len);
	}
	for(re int i=1;i<=m;++i)
	{
		l=read(),r=read(),k=read();
		query(rt[l-1],rt[r],1,len,k);
		printf("%d\n",b[ans]);
	}
	return 0;
}

10.22

\(ST\)

\(f[i][j]\)表示以\(i\)为左端点,长度(包括左端点)为\(2^j\)的区间最大值

#include<cstdio>
#include<iostream>
#define re register
#define maxn 1001000
using namespace std;
inline int read()
{
	int x=0,f=1; char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
int n,m,num,tmp,l,r,a[maxn],f[maxn][23],ans;
int lg[maxn<<1],s;
int main()
{
	n=read(),m=read();
	for(re int i=1;i<=n;++i) a[i]=read(),f[i][0]=a[i];
	lg[0]=-1;//lg数组预处理
	for(re int i=1;i<=n;++i)
	lg[i]=lg[i>>1]+1;
	for(re int j=1;j<=23;++j)
	 for(re int i=1;i+(1<<j)-1<=n;++i)
	  f[i][j]=max(f[i][j-1],f[i+(1<<j-1)][j-1]);//注意是i+(1<<j-1),因为表示的是左端点
	for(re int i=1;i<=m;++i)
	{
		l=read(),r=read();
		s=lg[r-l+1];
		printf("%d\n",max(f[l][s],f[r-(1<<s)+1][s]));
	}
}

树的数据生成器(带权)

#include<cstdlib>
#include<iostream>
#include<ctime>
#include<cstdio>
#include<algorithm>
#define re register
using namespace std;
int n,q,qx,qy,w;
int x[10010],y[10010],z[10010];
int a[10010],fa[10010];
int cnt2,cnt;
struct Edge{
	int u,v,w;
}e[10010];
int find(int x)
{
	return fa[x]==x?x:find(fa[x]);
}
int flag;
int main()
{
	srand(time(0));
	n=rand()%10+1;
	printf("%d\n",n);
	for(re int i=1;i<=n;++i)
	{
		for(re int j=i+1;j<=n;++j)
		{
			x[++cnt]=i;
			y[cnt]=j;
			z[cnt]=rand()%100+1;
		}
	}
	for(re int i=1;i<=cnt;++i) a[i]=i,fa[i]=i;
	random_shuffle(a+1,a+cnt+1);
	for(re int i=1;i<=cnt;++i)
	{
		int pos=a[i];
		int eu=find(x[pos]),ev=find(y[pos]);
		if(eu==ev) continue;
		fa[ev]=eu;
		e[++cnt2].u=x[pos],e[cnt2].v=y[pos],e[cnt2].w=z[pos];
		if(cnt2==n-1) break;
	}
	for(re int i=1;i<=cnt2;++i)
	{
		printf("%d %d %d\n",e[i].u,e[i].v,e[i].w);
	}
	
	return 0;
}

无向图生成

#include<cstdlib>
#include<iostream>
#include<ctime>
#include<cstdio>
#include<algorithm>
#define re register
using namespace std;
int n,q,qx,qy,w,m;
int x[10010],y[10010],z[10010];
int a[10010],fa[10010];
int cnt2,cnt;
struct Edge{
	int u,v;
}e[10010];
int find(int x)
{
	return fa[x]==x?x:find(fa[x]);
}
int flag;
int main()
{
	srand(time(0));
	n=rand()%10+1;
	printf("%d ",n);
	for(re int i=1;i<=n;++i)
	{
		for(re int j=i+1;j<=n;++j)
		{
			x[++cnt]=i;
			y[cnt]=j;
			//z[cnt]=rand()%100+1;
		}
	}
	for(re int i=1;i<=cnt;++i) a[i]=i,fa[i]=i;
	random_shuffle(a+1,a+cnt+1);
	int i; 
	for(i=1;i<=cnt;++i)
	{
		int pos=a[i];
		int eu=find(x[pos]),ev=find(y[pos]);
		if(eu==ev) continue;
		fa[ev]=eu;
		e[++cnt2].u=x[pos],e[cnt2].v=y[pos];
		if(cnt2==n-1) break;
	} 
	m = n - 1;
	while(++i<=cnt)
	{
		int pos = a[i];
		m++;
		e[++cnt2].u=x[pos],e[cnt2].v=y[pos];
	}
	printf("%d\n",m);
	for(re int i=1;i<=cnt2;++i)
	{
		printf("%d %d\n",e[i].u,e[i].v);
	}
	
	return 0;
}

10.26

权值线段树求区间内权值\(\in [l,r]\)的数的个数

long long query(int x,int ql,int qr)
{
    if(ql<=t[x].l&&t[x].r<=qr) return t[x].v;
    int mid=(t[x].l+t[x].r)>>1;
    long long tmp=0;
    if(ql<=mid) tmp+=query(lson,ql,qr);
    if(mid<qr)  tmp+=query(rson,ql,qr);
    return tmp;
}

快速输出
一般用不着,\(int128\)只能这样输出

inline void write(ll x)
{
    if(x<0)
    {
        putchar('-');
        x=-x;
    }
    if(x>9)
    {
        write(x/10);
    }
    putchar(x%10+'0');//回溯时输出确保顺序对
}

\(11.3\)

扫描线:用于处理方格图矩形问题

注意:
\(1.\)和普通线段树相比特殊的是递归到边界也要上传,因此空间开的足够大(\(8\)倍)

\(2.\)每个点存储一个长度为\(1\)的区间,注意区间与点之间的转换

\(3.\)注意开好\(long\ long\)

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#define re register
#define maxn 200100
#define ls p<<1
#define rs p<<1|1
#define ll long long
using namespace std;
inline int read()
{
	int x=0,f=1; char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
};
ll tmp,ans;
int n,x_,y_,x__,y__,cnt1,cnt2,sums[maxn<<2],val[maxn],len,tmp1,tmp2,lenn[maxn<<2],cnt[maxn<<2];
struct node{
	int x,yu,yd,k;
}e[maxn<<2];
bool cmp(node A,node B)
{
	return A.x<B.x;
}
void push_up(int p,int l,int r)
{
	if(cnt[p]) lenn[p]=val[r+1]-val[l];//因为是区间长度,所以要把右节点+1才变回原来右边界 
	else lenn[p]=lenn[ls]+lenn[rs];
}
void update(int p,int l,int r,int ql,int qr,int k)
{
	if(ql<=l&&r<=qr)
	{
		cnt[p]+=k;
		push_up(p,l,r);//这里别忘上传,主要是更新当前节点的lenn 
		return;
	}
	int mid=(l+r)>>1;
	if(ql<=mid) update(ls,l,mid,ql,qr,k);
	if(qr>mid) update(rs,mid+1,r,ql,qr,k);
	push_up(p,l,r);
}
int main()
{
   n=read();
   for(re int i=1;i<=n;++i)
   {
   	  x_=read(),y_=read(),x__=read(),y__=read();
   	  e[++cnt1].x=x_,e[cnt1].yu=y__,e[cnt1].yd=y_,e[cnt1].k=1;
   	  e[++cnt1].x=x__,e[cnt1].yu=y__,e[cnt1].yd=y_,e[cnt1].k=-1;
   	  val[++cnt2]=y_,val[++cnt2]=y__;
   }
   sort(val+1,val+cnt2+1);
   len=unique(val+1,val+cnt2+1)-val-1;
   for(re int i=1;i<=(n<<1);++i)
   {
   	 tmp1=lower_bound(val+1,val+len+1,e[i].yu)-val;
   	 tmp2=lower_bound(val+1,val+len+1,e[i].yd)-val;
   	 e[i].yu=tmp1,e[i].yd=tmp2;
   }
   sort(e+1,e+cnt1+1,cmp);
   for(re int i=1;i<(n<<1);++i)
   {
   	   update(1,1,(n<<1),e[i].yd,e[i].yu-1,e[i].k);//因为一个点代表一个区间,所以原来n个点在线段树上是n-1个点 
   	   ans+=(ll)lenn[1]*(e[i+1].x-e[i].x);//这里long long很重要 
   }
   printf("%lld\n",ans);
   return 0;	
} 

11.5
\(trie\)字典树,还是有可能考到的

可以很好存储字符串,比较好维护公共前缀,在上面可以搞一些\(kmp\)(\(AC\)自动机),\(dp\)\(lca\)的操作,很多异或操作也可以用\(01trie\)维护

1.读入时找到答案别乱\(break\),不然剩下的数据会到下一组读入就\(GG\)

2.\(trie\)节点编号初始化为\(1\)

#include<cstdio>
#include<iostream>
#include<cstring>
#define re register
#define maxn 100010 
using namespace std;
inline int read()
{
	int x=0,f=1; char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
int T,n,ch[maxn][26],tot;
char a[220];
bool flag,bo[maxn];
void insert(char *s)
{
	int u=1,len=strlen(s);
	for(int i=0;i<len;++i)
	{
		if(!ch[u][s[i]-'0']) ch[u][s[i]-'0']=++tot;
		else if(i==len-1)//只要前面走了没有的分支就永远不会出现这个,出现这个代表这len位都匹配上了,当前字符串是前面字符串的前缀 
		flag=true;
		u=ch[u][s[i]-'0'];
		if(bo[u]) flag=true;//表示之前字符串是当前前缀,注意在跳一格之后再判断,因为第i(从1开始数)层循环结束后跳到字符的i位 
	}
	bo[u]=true;//标记结尾 
	return;
}
int main()
{
	T=read();
	while(T--)
	{
		memset(ch,0,sizeof(ch));
		memset(bo,0,sizeof(bo));
		flag=false;
		tot=1;//初始化为1!!!!因为第一个节点无信息 
		n=read();
		for(re int i=1;i<=n;++i)
		{
			scanf("%s",a);//后面传指针,就别空一位读入了 
			insert(a);//把指针传进去,是从0位置开始的 
		} 
		if(!flag) printf("YES\n");
		else printf("NO\n");
	}
	return 0;
}

11.6

\(AC\)自动机(简单版)--统计有多少个模式串出现过

\(AC\)自动机是\(trie\)上的\(kmp\),注意\(ch[u][i]\)代表的可能不是点,而是一个后继装套,通过\(build\)函数建立失配指针同时连字典图的边,便于转移,简化了\(fail[fail[fail[i]]]\)一直跳的过程

AC 自动机的时间复杂度在需要找到所有匹配位置时是\(O(|S|+m)\),其中\(|S|\) 表示文本串的长度,\(m\)表示模板串的总匹配次数;而只需要求是否匹配时时间复杂度为 O(|S|)。

#include<cstdio>
#include<iostream>
#include<queue>
#include<cstring>
#include<string>
#define re register
#define maxn 1001000
using namespace std;
inline int read()
{
	int x=0,f=1; char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
int ch[maxn][26],n,fail[maxn],tot,bo[maxn];
char s[maxn];
void insert(char *s)
{
	int u=0,len=strlen(s);
	for(re int i=0;i<len;++i)
	{
		int c=s[i]-'a';
		if(!ch[u][c]) ch[u][c]=++tot;
		u=ch[u][c];
	}
	bo[u]++;
}
queue<int> q;
void build()
{
	for(re int i=0;i<26;++i)
	if(ch[0][i]) q.push(ch[0][i]);
	while(!q.empty())
	{
		int u=q.front();
		q.pop();
		for(re int i=0;i<26;++i)
		{
			if(ch[u][i])
			 fail[ch[u][i]]=ch[fail[u]][i],q.push(ch[u][i]);//正常的失配指针 
			else
			 ch[u][i]=ch[fail[u]][i];//连字典图的边 
			 
		}
	}
}
int AC(char *s)
{
	int ans=0,len=strlen(s),u=0;
	for(re int i=0;i<len;++i)
	{
			u=ch[u][s[i]-'a'];//找下一个状态 
			for(re int j=u;j&&bo[j]!=-1;j=fail[j])//对于所有相同后缀统计答案,如果出现-1代表后面都是-1,不用统计了 
			ans+=bo[j],bo[j]=-1;//加上答案,标记 
	}
	return ans;
}
int main()
{
	n=read();
	for(re int i=1;i<=n;++i) 
	{
		scanf("%s",s);
		insert(s);
	}
	build();
	scanf("%s",s);
	printf("%d\n",AC(s));
	return 0;
}

\(AC\)自动机(强化版)--统计每个字符串出现次数以及出现次数最多的字符串

做法就是到一个字符串结尾时标记这个字符串的编号,\(bo\)数组不要赋值成\(-1\),因为要多次匹配,每次匹配到让\(cnt[id[u]]+=bo[u]\),最后找\(max\)输出即可,注意出现重复模式串的情况

复杂度:\(O(|S|+m)\),其中\(m\)为匹配次数

#include<cstdio>
#include<iostream>
#include<cstring>
#include<string>
#include<algorithm>
#include<queue> 
#define maxn 1001000
#define maxm 201000
#define re register
using namespace std;
inline int read()
{
	int x=0,f=1; char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
int ch[maxm][26],bo[maxm],n,tot,ans,id[maxm],maxx,cnt[220];
int fail[maxm];
char s[220][220],t[maxn];
void insert(char *s,int k)
{
	int u=0,len=strlen(s);
	for(re int i=0;i<len;++i)
	{
		int c=s[i]-'a';
		if(!ch[u][c]) ch[u][c]=++tot;
		u=ch[u][c];
	}
	bo[u]++;
	if(!id[u]) id[u]=k;//题目要求按顺序输出,可能有重复模式串,所以输出最靠前一个,只用记录最靠前的编号即可 
}
queue<int> q;
void build()
{
	for(re int i=0;i<26;++i)
	if(ch[0][i]) q.push(ch[0][i]);
	while(!q.empty())
	{
		int u=q.front();
		q.pop();
		for(re int i=0;i<26;++i)
		{
			if(ch[u][i])
			  fail[ch[u][i]]=ch[fail[u]][i],q.push(ch[u][i]);
			else 
			  ch[u][i]=ch[fail[u]][i];
		}
	}
}
void AC(char *s)
{
	int u=0,len=strlen(s);
    for(re int i=0;i<len;++i)
	{
		u=ch[u][s[i]-'a'];
		for(int j=u;j;j=fail[j])
		{
			cnt[id[j]]+=bo[j];//不写+1也是为了应对出现重复模式串的情况 
			maxx=max(maxx,cnt[id[j]]);//对应编号计数
			//注意这里bo[j]会用多次,不要标记 
		}
	}	
}
int main()
{
	while(1)
	{
		n=read();
		if(n==0) break;
		memset(bo,0,sizeof(bo));
		memset(ch,0,sizeof(ch));
		memset(id,0,sizeof(id));//记录trie树上节点对应字符串的编号 
		memset(cnt,0,sizeof(cnt));//记录出现次数 
		memset(fail,0,sizeof(fail));
		tot=ans=maxx=0;
		for(re int i=1;i<=n;++i)
		{
			scanf("%s",s[i]);
			insert(s[i],i);
		}
		build();
        scanf("%s",t);
		AC(t);
		printf("%d\n",maxx);
		for(re int i=1;i<=n;++i) if(cnt[i]==maxx) printf("%s\n",s[i]);
	}
	return 0;
} 
posted @ 2019-10-12 14:07  __Liuz  阅读(407)  评论(2编辑  收藏  举报