LCA:倍增

lca

先给张图(声明luogu版权)
在这里插入图片描述
lca是什么呢,就是在一棵树里,两个节点的最近公共祖先
比如说,在上图中,4和5的lca就是2,8和10的lca就是1(很好理解对吗)

lca主要有这样一些解决的方法

向上标记法
顾名思义,从x向上走,走到根节点,标记。从y向上走,走到根节点,标记,第一次遇到标记过的点时,就是lca(x,y)
但是…时间上…卡一卡可以卡到O(n)
还是太慢了

倍增法

一个非常实用的算法
设f(x,k)为x的2k辈祖先,显然f(x,0) is x’s father
另外,f(x,k)=f(f(x,k-1),k-1)
这里可以使用一个dfs进行预处理,复杂度为O(n log n)

inline void dfs(int u,int fa){
	f[u][0]=fa;
	depth[u]=depth[fa]+1;
	for(int i=1;(1<<i)<=depth[u];i++) f[u][i]=f[f[u][i-1]][i-1];
	for(int i=head[u];~i;i=e[i].next) if(e[i].to!=fa)dfs(e[i].to,u);
}

接下来就是查询,是一个在线的操作,每次查询复杂度为log(n)
伪代码:

first let depth[x]>=depth[y]
second let depth[x]=depth[y]
if x=y print x
else push x,y up to there LCA's sons
print x's father
版权归属:kkksc03&&chen_zhe

真代码:

inline int lca(int x,int y){
	if(depth[x]<depth[y]) swap(x,y);
	while(depth[x]>depth[y]) x=f[x][lg[depth[x]-depth[y]]-1];
	if(x==y) return x;
	_Rep(k,lg[depth[x]]-1,0) if(f[x][k]!=f[y][k]) x=f[x][k],y=f[y][k];
	return f[x][0];
}

这里因为有查询log的操作,这里是加了一个常数优化

Rep(i,1,n) lg[i]=lg[i-1]+(1<<lg[i-1]==i);

这里应该挺好理解的,就不多说了

完整代码 luogu LCA模板

# include <cstdio>
# include <algorithm>
# include <cstring>
# include <cmath>
# include <climits>
# include <iostream>
# include <cstring>
# include <queue>
# include <vector>
# include <set>
# include <map>
# include <cstdlib>
# include <stack>
# include <ctime>
using namespace std;

# define Rep(i,a,b) for(int i=a;i<=b;i++)
# define _Rep(i,a,b) for(int i=a;i>=b;i--)
# define mct(a,b) memset(a,b,sizeof(a))
# define gc getchar()
typedef long long ll;
const int N=5e5+5;
const int inf=0x7fffffff;
inline int read(){
	int s=0,w=1;
	char c=gc;
	while(c<'0'||c>'9'){if(c=='-')w=-1;c=gc;}
	while(c>='0'&&c<='9')s=s*10+c-'0',c=gc;
	return s*w;
}
int n,m,s;
int head[N],cnt,lg[N],f[N][20],depth[N];
struct Edge{
	int to,next;
}e[N<<1];

inline void add(int x,int y){
	e[++cnt]=(Edge){y,head[x]},head[x]=cnt;
}

inline void dfs(int u,int fa){
	f[u][0]=fa;
	depth[u]=depth[fa]+1;
	for(int i=1;(1<<i)<=depth[u];i++) f[u][i]=f[f[u][i-1]][i-1];
	for(int i=head[u];~i;i=e[i].next) if(e[i].to!=fa)dfs(e[i].to,u);
}

inline int lca(int x,int y){
	if(depth[x]<depth[y]) swap(x,y);
	while(depth[x]>depth[y]) x=f[x][lg[depth[x]-depth[y]]-1];
	if(x==y) return x;
	_Rep(k,lg[depth[x]]-1,0) if(f[x][k]!=f[y][k]) x=f[x][k],y=f[y][k];
	return f[x][0];
}

int main()
{
	mct(head,-1);
	n=read(),m=read(),s=read();
	Rep(i,1,n-1){
		int u=read(),v=read();
		add(u,v);
		add(v,u);
	}
	
	dfs(s,0);
	
	Rep(i,1,n) lg[i]=lg[i-1]+(1<<lg[i-1]==i);
	
	Rep(i,1,m){
		int u=read(),v=read();
		printf("%d\n",lca(u,v));
	}
	return 0;
}

树链剖分
树剖也可以求LCA,虽然效率也是O(log n)查询+O(nlogn)预处理,但是常数稍微小一点

虽然本蒟蒻不会,但还是贴个树剖代码吧…
树剖模板

//树链剖分
# include <cstdio>
# include <algorithm>
# include <cstring>
# include <cmath>
# include <iostream>
# define Rep(i,a,b) for(int i=a;i<=b;i++)
# define _Rep(i,a,b) for(int i=a;i>=b;i--)
using namespace std;

const int N=2e5+5;
inline int read() {
	int s=0,w=-1;
	char c=getchar();
	while(c<'0'||c>'9'){if(c=='-')w=-1;c=getchar();}
	while(c>='0'&&c<='9')s=s*10+c-'0',c=getchar();
	return s*w;
}

struct Edge{
	int to,next;
}e[2*N];

int cnt,n,m,r,mod,head[N],w[N],wt[N],res;//w表示原来的权重,wt表示线段树的权重 
void add(int x,int y){
	e[++cnt]=(Edge){y,head[x]},head[x]=cnt;
}

int t[4*N],laz[4*N];//线段树,懒标记 
int son[N],id[N],faz[N],dep[N],top[N],siz[N],give_num;//树链剖分数组 

//=========================================================华丽的分割线 
//线段树.begin() 
# define mid ((l+r)>>1)
# define lson rt<<1,l,mid
# define rson rt<<1|1,mid+1,r
# define len (r-l+1)
void push_up(int rt,int lens){
    laz[rt<<1]+=laz[rt];
    laz[rt<<1|1]+=laz[rt];
    t[rt<<1]+=laz[rt]*(lens-(lens>>1));
    t[rt<<1|1]+=laz[rt]*(lens>>1);
    t[rt<<1]%=mod;
    t[rt<<1|1]%=mod;
    laz[rt]=0;
}//push_up操作 

void make_tree(int rt,int l,int r){
    if(l==r){
        t[rt]=wt[l];
        if(t[rt]>mod)t[rt]%=mod;
        return;
    }
    make_tree(lson);
    make_tree(rson);
    t[rt]=(t[rt<<1]+t[rt<<1|1])%mod;
}

void ask(int rt,int l,int r,int L,int R){
    if(L<=l&&r<=R){res+=t[rt];res%=mod;return;}
    else{
        if(laz[rt])push_up(rt,len);
        if(L<=mid)ask(lson,L,R);
        if(R>mid)ask(rson,L,R);
    }
}
void update(int rt,int l,int r,int L,int R,int k){
    if(L<=l&&r<=R){
        laz[rt]+=k;
        t[rt]+=k*len;
    }
    else{
        if(laz[rt])push_up(rt,len);
        if(L<=mid)update(lson,L,R,k);
        if(R>mid)update(rson,L,R,k);
        t[rt]=(t[rt<<1]+t[rt<<1|1])%mod;
    }
}
//线段树.end() 
//=========================================================华丽的分割线 
//各种操作.begin() 
void work_one(int x,int y,int k){//第一种操作 
	k%=mod;
	while(top[x]!=top[y]){
		if(dep[top[x]]<dep[top[y]])swap(x,y);
		update(1,1,n,id[top[x]],id[x],k);
		x=faz[top[x]];
	}
	if(dep[x]>dep[y])swap(x,y);
	update(1,1,n,id[x],id[y],k);
}

int work_two(int x,int y){
    int ans=0;
    while(top[x]!=top[y]){
        if(dep[top[x]]<dep[top[y]])swap(x,y);
        res=0;
        ask(1,1,n,id[top[x]],id[x]);
        ans+=res;
        ans%=mod;
        x=faz[top[x]];
    }
    if(dep[x]>dep[y])swap(x,y);
    res=0;
//    cout<<"id[x]="<<id[x]<<" id[y]=" <<id[y]<<endl;
    ask(1,1,n,id[x],id[y]);
    ans+=res;
    return ans%mod;
}

void work_three(int x,int k){
	//cout<<"id[x]="<<id[x]<<" "<<"size[x]="<<siz[x]<<endl;
	update(1,1,n,id[x],id[x]+siz[x]-1,k);//子树区间右端点为id[x]+siz[x]-1 
}

int work_four(int x){
	res=0;
	ask(1,1,n,id[x],id[x]+siz[x]-1);
	return res%mod;
}
//各种操作.end() 
//=========================================================华丽的分割线 
//树链刨分分割.begin() 
void dfs1(int u,int father,int depth){
	faz[u]=father; 
	dep[u]=depth;
	siz[u]=1;
	int hevson=-1;//重儿子 
	for(int i=head[u];i;i=e[i].next){
		int v=e[i].to;
		if(v==father)continue;//如果是父亲节点就继续 
		dfs1(v,u,depth+1);
		siz[u]+=siz[v];//把儿子(后代)的数量加起来 
		if(siz[v]>hevson)son[u]=v,hevson=siz[v];
	}
}

void dfs2(int u,int top_chain){
	give_num++;
	id[u]=give_num; 
	//cout<<u<<"-->"<<give_num<<endl;
	wt[give_num]=w[u];
//	cout<<wt[give_num]<<" "<<u<<endl;
	//线段树更新每个点的id 
	top[u]=top_chain;
	if(!son[u])return;//如果没有儿子直接return 
	dfs2(son[u],top_chain);//重儿子继续这个链 
	for(int i=head[u];i;i=e[i].next){
		int v=e[i].to;
		if(v==faz[u]||v==son[u])continue;
		dfs2(v,v);
		//找轻儿子,每个轻儿子都是一个链头 
	}
}

//树链剖分分割.end()
//=========================================================华丽的分割线 
//主函数.begin() 
int main()
{
	scanf("%d%d%d%d",&n,&m,&r,&mod);
	Rep(i,1,n) scanf("%d",&w[i]);
	Rep(i,1,n-1){
		int x,y;
		scanf("%d%d",&x,&y);
		add(x,y);
		add(y,x);
	}
	dfs1(r,0,1);
	dfs2(r,r);
	make_tree(1,1,n);
	Rep(i,1,m){
		//Rep(i,1,n)cout<<"t["<<i<<"]="<<t[i]<<" ";cout<<endl;
		int pd=3,x,y,z;
		cin>>pd;
		if(pd==1){
			scanf("%d%d%d",&x,&y,&z);
			work_one(x,y,z);
		}
		else if(pd==2){
			scanf("%d%d",&x,&y);
			printf("%d\n",work_two(x,y));
		}
		else if(pd==3){
			scanf("%d%d",&x,&y);
			//printf("orzgjm\n");
			work_three(x,y);
		}
		else{
			scanf("%d",&x);
			printf("%d\n",work_four(x));
		}
	}
	return 0;
}





发布了18 篇原创文章 · 获赞 3 · 访问量 1267

猜你喜欢

转载自blog.csdn.net/devout_/article/details/90575968