【双树问题-树剖+线段树合并】CC_EDGEST Edges in Spanning Trees

【题目】
Codechef
给定相同点集(大小为 n n n)上两棵生成树 T 1 , T 2 T_1,T_2 T1,T2。对于 T 1 T_1 T1中每条边,求 T 2 T_2 T2中有多少条边满足:

  • T 1 − e 1 + e 2 T_1-e_1+e_2 T1e1+e2(从 T 1 T_1 T1中删去 e 1 e_1 e1再加上 e 2 e_2 e2)是一棵生成树
  • T 2 − e 2 + e 1 T_2-e_2+e_1 T2e2+e1是一棵生成树

n ≤ 2 × 1 0 5 n\leq 2\times 10^5 n2×105

【解题思路】
以下每个点维护父边信息。

若两条边可以互相替代,则满足 e 1 e_1 e1 T 1 T_1 T1 e 2 e_2 e2两端点的路径上, e 2 e_2 e2同理。

我们不妨只考虑第一个限制,那么这个问题就是一个树链 + 1 +1 +1的问题,怎么做都可以,这里可以使用树上差分,即在路径端点处打上 + 1 +1 +1,在 l c a lca lca处打上 − 2 -2 2

现在加入第二个限制,实际上就是要在第一个限制的基础上求出有多少条边满足第二个限制,即 e 1 e_1 e1 T 2 T_2 T2上的路径中有多少条边在差分中计算到了。实际上我们在差分的时候,每遇到一个 + + +标记,我们可以在其 T 2 T_2 T2的对应边上 + 1 +1 +1,每遇到一个 − - 标记,那么就在对应边上 − 1 -1 1。这样做的话, e 1 e_1 e1在上的 T 2 T_2 T2路径所贡献到的边,实际上就是对应路径的权值。

总的来说,我们要做的,就是对 T 1 T_1 T1进行 dfs \text{dfs} dfs和打标记,对 T 2 T_2 T2进行单点修改,路径查询。而在 dfs \text{dfs} dfs过程中, T 2 T_2 T2的线段树我们要进行线段树合并,因为每个节点得到的树剖线段树是独立的。

路径查询写一个树链剖分就可以了,复杂度 O ( n log ⁡ 2 n ) O(n\log ^2 n) O(nlog2n)
就是要写一会。

【参考代码】

#include
#define pb push_back
using namespace std;

const int N=2e5+10,M=N*60;

namespace IO
{
	int read()
	{
		int ret=0;char c=getchar();
		while(!isdigit(c)) c=getchar();
		while(isdigit(c)) ret=ret*10+(c^48),c=getchar();
		return ret;
	}
	void write(int x){if(x>9)write(x/10);putchar(x%10^48);}
	void writesp(int x){write(x);putchar(' ');}
}
using namespace IO;

namespace Segment
{
	int rt[N];
	struct tr
	{
		int sz,sum[M],ls[M],rs[M];
		void update(int &x,int l,int r,int p,int v)
		{
			if(!x) x=++sz;sum[x]+=v;
			if(l==r) return;
			int mid=(l+r)>>1;
			if(p<=mid) update(ls[x],l,mid,p,v);
			else update(rs[x],mid+1,r,p,v);
		}
		int query(int x,int l,int r,int L,int R)
		{
			//printf("Q:%d %d %d %d %d\n",x,l,r,L,R);
			if(L>R || !x) return 0;
			if(L<=l && r<=R) return sum[x];
			int mid=(l+r)>>1,res=0;
			if(L<=mid) res+=query(ls[x],l,mid,L,R);
			if(R>mid) res+=query(rs[x],mid+1,r,L,R);
			return res;
		}
		int merge(int x,int y,int l,int r)
		{
			if(!x || !y) return x+y;
			int mid=(l+r)>>1,z=++sz;
			sum[z]=sum[x]+sum[y];
			if(l^r)	ls[z]=merge(ls[x],ls[y],l,mid),rs[z]=merge(rs[x],rs[y],mid+1,r);
			return z;
		}
		void clear()
		{
			for(int i=0;i<=sz;++i) sum[i]=ls[i]=rs[i]=0;
			sz=0;
		}
		void print(int x,int l,int r)
		{
			printf("%d %d %d %d\n",x,l,r,sum[x]);
			if(l==r) return;
			int mid=(l+r)>>1;
			print(ls[x],l,mid);print(rs[x],mid+1,r);
		}
	}tr;
}
using namespace Segment;

namespace Tree
{
	struct Tway{int v,nex,id;};
	struct Tree
	{
		int tot,ind;
		int head[N],top[N],son[N],fa[N],siz[N],pos[N],dep[N];
		Tway e[N<<1];
		void add(int u,int v,int id)
		{
			e[++tot]=(Tway){v,head[u],id};head[u]=tot;
			e[++tot]=(Tway){u,head[v],id};head[v]=tot;
		}
		void dfs1(int x)
		{
			siz[x]=1;
			for(int i=head[x];i;i=e[i].nex)
			{
				int v=e[i].v;
				if(v==fa[x]) continue;
				fa[v]=x;dep[v]=dep[x]+1;dfs1(v);siz[x]+=siz[v];
				if(siz[v]>siz[son[x]]) son[x]=v;
			}
		}
		void dfs2(int x,int tp)
		{
			top[x]=tp;pos[x]=++ind;
			if(son[x]) dfs2(son[x],tp);
			for(int i=head[x];i;i=e[i].nex)
			{
				int v=e[i].v;
				if(v==fa[x] || v==son[x]) continue;
				dfs2(v,v);
			}
		}
		void build(){dfs1(1);dfs2(1,1);}
		int lca(int x,int y)
		{
			while(top[x]^top[y])
			{
				if(dep[top[x]]>=dep[top[y]])x=fa[top[x]];
				else y=fa[top[y]];
			}
			return dep[x]<dep[y]?x:y;
		}
		void clear()
		{
			for(int i=0;i<=ind;++i) head[i]=top[i]=dep[i]=fa[i]=siz[i]=son[i]=pos[i]=0;
			tot=ind=0;
		}
	}T1,T2;
}
using namespace Tree;

namespace DreamLolita
{
	int n,ans[N],fr[N];
	vector<int>tag[N];
	int querychain(int root,int x,int y)
	{
		//printf("query:%d %d\n",x,y);
		int res=0;
		while(T2.top[x]^T2.top[y])
		{
			if(T2.dep[T2.top[x]]<T2.dep[T2.top[y]]) swap(x,y);//should jump x
			res+=tr.query(root,1,n,T2.pos[T2.top[x]],T2.pos[x]);x=T2.fa[T2.top[x]];
		}
		if(T2.dep[x]<T2.dep[y]) swap(x,y);
		//printf("%d %d %d\n",root,T2.pos[y]+1,T2.pos[x]);
		res+=tr.query(root,1,n,T2.pos[y]+1,T2.pos[x]);//-1 because no lca
		return res;
	}
	void dfstag(int x)//put tag on T1,so dfs T2
	{
		for(int i=T2.head[x];i;i=T2.e[i].nex)
		{
			int v=T2.e[i].v,d=T2.e[i].id;
			if(v==T2.fa[x]) continue;
			tag[v].pb(d);tag[x].pb(d);tag[T1.lca(x,v)].pb(-d);
			fr[d]=v;dfstag(v);//point v maintain edge d on T2
		}
	}
	void dfs(int x,int d)//calc ans,so dfs T1,and add  on T2,query on T2,use Heavy_Light cut
	{
		for(int i=T1.head[x];i;i=T1.e[i].nex)//first dfs then calc
		{
			int v=T1.e[i].v;
			if(v==T1.fa[x]) continue; 
			dfs(v,T1.e[i].id);rt[x]=tr.merge(rt[x],rt[v],1,n);
		}
		if(x==1) return;
		for(auto i:tag[x])//push tag,+1 or -2
		{
			if(i>0) tr.update(rt[x],1,n,T2.pos[fr[i]],1);
			else tr.update(rt[x],1,n,T2.pos[fr[-i]],-2);
		}
		//printf("now:%d\n",x);tr.print(rt[x],1,n);puts("");
		ans[d]=querychain(rt[x],x,T1.fa[x]);
	}
	void clear()
	{
		T1.clear();T2.clear();tr.clear();
		for(int i=0;i<=n;++i) tag[i].clear(),fr[i]=0,rt[i]=0;
	}
	void solution()
	{
		n=read();
		for(int i=1;i<n;++i) T1.add(read(),read(),i);
		for(int i=1;i<n;++i) T2.add(read(),read(),i);
		T1.build();T2.build();dfstag(1);dfs(1,0);
		for(int i=1;i<n;++i) writesp(ans[i]); puts("");
		clear();
	}
}


int main()
{
#ifndef ONLINE_JUDGE
	freopen("CC_EDGEST.in","r",stdin);
	freopen("CC_EDGEST.out","w",stdout);
#endif
	int T=read();
	while(T--) DreamLolita::solution();
	return 0;
}

你可能感兴趣的:(Tree-树链剖分,数据结构-线段树)