点此看题面

大致题意: 给你两棵\(n\)个点的树,对于第一棵树中的每条边\(e_1\),求存在多少条第二棵树中的边\(e_2\),使得第一棵树删掉\(e_1\)加上\(e_2\)、第二棵树删掉\(e_2\)加上\(e_1\)后皆仍为生成树。

题意转化

考虑对于\(e_1(x,y)\),合法的\(e_2(u,v)\),必然存在于第二棵树中\(x\)到\(y\)的路径之上。

同理,则\(e_1\)也应该存在于第一棵树中\(u\)到\(v\)的路径之上。

考虑到我们是在枚举\(e_1\),而每条边都可以被表示为\((x,fa_x)\)的形式。

也就是说,若\(e_1\)存在于第一棵树中\(u\)到\(v\)的路径之上,则\(u,v\)应该一个存在于\(x\)子树内,一个在\(x\)子树外。

那么题意就变成了,对于第一棵树中的每一个点\(x\),设其在第一棵树中的父节点为\(fa_x\),求在第二棵树中\(x\)到\(fa_x\)路径存在多少条边\((u,v)\),满足\(u,v\)一个存在于\(x\)子树内,一个在\(x\)子树外。

树链剖分+树上启发式合并

考虑对第二棵树进行树链剖分,对一棵树进行树上启发式合并

则我们处理每一个\(x\)时,此时第二棵树的树剖线段树上,第一棵树中\(x\)子树内点为\(1\),\(x\)子树外点为\(0\)。

然后我们可以树剖抠出第二棵树中的\(x\)到其第一棵树中父亲的路径,然后求出有多少对相邻的\(01\)。

合并两个区间时,我们只要记录每个区间的答案及其左右端值,然后比较左区间右端和右区间左端是否相同即可。

注意树剖抠出路径的时候有些细节需要注意,对于两个点向上跳的过程要分别进行统计,且最后合并两个点向上跳区间时要先将其中一个点左右端值交换一下再合并。

代码

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 200000
#define LL long long
#define RL Reg LL
#define CL Con LL&
#define add(x,y) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y)
#define swap(x,y) (x^=y^=x^=y)
using namespace std;
int n;struct edge {int to,nxt;};
class FastIO
{
private:
#define FS 100000
#define tc() (A==B&&(B=(A=FI)+fread(FI,1,FS,stdin),A==B)?EOF:*A++)
#define pc(c) (C==E&&(clear(),0),*C++=c)
#define tn (x<<3)+(x<<1)
#define D isdigit(c=tc())
int T;char c,*A,*B,*C,*E,FI[FS],FO[FS],S[FS];
public:
I FastIO() {A=B=FI,C=FO,E=FO+FS;}
Tp I void read(Ty& x) {x=0;W(!D);W(x=tn+(c&15),D);}
Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
Tp I void write(Ty x) {W(S[++T]=x%10+48,x/=10);W(T) pc(S[T--]);}
Tp I void write(Con Ty& x,Con char& y) {write(x),pc(y);}
I void clear() {fwrite(FO,1,C-FO,stdout),C=FO;}
}F;
class Tree1//题意中的第二棵树,树链剖分
{
private:
int ee,d,lnk[N+5],Sz[N+5],son[N+5],fa[N+5],dep[N+5],dfn[N+5],Top[N+5];edge e[N<<1];
struct Il//区间
{
int L,R,S;I Il(CI x=0,CI y=0,CI v=0):L(x),R(y),S(v){}
I Il operator + (Con Il& o) {return ~S?(~o.S?Il(L,o.R,S+o.S+(R^o.L)):*this):o;}//合并区间
};
template<int SZ> class SegmentTree//树剖线段树
{
private:
#define LT l,mid,rt<<1
#define RT mid+1,r,rt<<1|1
#define PU(x) (O[x]=O[x<<1]+O[x<<1|1])
int n;Il O[SZ<<2];
I void Upt(CI x,CI v,CI l,CI r,CI rt)//单点修改
{
if(l==r) return (void)(O[rt].L=O[rt].R=v);RI mid=l+r>>1;
x<=mid?Upt(x,v,LT):Upt(x,v,RT),PU(rt);
}
I Il Qry(CI tl,CI tr,CI l,CI r,CI rt)//扣区间
{
if(tl==l&&r==tr) return O[rt];RI mid=l+r>>1;
if(tr<=mid) return Qry(tl,tr,LT);if(tl>mid) return Qry(tl,tr,RT);
return Qry(tl,mid,LT)+Qry(mid+1,tr,RT);
}
public:
I void Build(CI _n) {n=_n;}I void Upt(CI x,CI v) {Upt(x,v,1,n,1);}
I Il Qry(CI l,CI r) {return Qry(l,r,1,n,1);}
};SegmentTree<N> S;
I void dfs1(CI x)//第一遍树剖dfs
{
for(RI i=(Sz[x]=1,son[x]=0,lnk[x]);i;i=e[i].nxt) e[i].to^fa[x]&&
(
dep[e[i].to]=dep[fa[e[i].to]=x]+1,dfs1(e[i].to),
Sz[x]+=Sz[e[i].to],Sz[e[i].to]>Sz[son[x]]&&(son[x]=e[i].to)
);
}
I void dfs2(CI x,CI col)//第二遍树剖dfs
{
Top[x]=col,dfn[x]=++d,son[x]&&(dfs2(son[x],col),0);
for(RI i=lnk[x];i;i=e[i].nxt) e[i].to^fa[x]&&
e[i].to^son[x]&&(dfs2(e[i].to,e[i].to),0);
}
public:
I void Clear() {memset(lnk,0,sizeof(lnk)),ee=d=0;}I void Add(CI x,CI y) {add(x,y);}
I void TreeChainDissection() {dfs1(1),dfs2(1,1),S.Build(n);}
I void Upt(CI x,CI v) {S.Upt(dfn[x],v);}//单点修改
I int Qry(RI x,RI y)//扣区间
{
Il tl,tr;tl.S=tr.S=-1;W(Top[x]^Top[y])
{
if(dep[Top[x]]>dep[Top[y]]) tl=S.Qry(dfn[Top[x]],dfn[x])+tl,x=fa[Top[x]];//记录第一个点向上跳的过程
else tr=S.Qry(dfn[Top[y]],dfn[y])+tr,y=fa[Top[y]];//记录第二个点向上跳的过程
}
dfn[x]>dfn[y]?(tl=S.Qry(dfn[y],dfn[x])+tl):(tr=S.Qry(dfn[x],dfn[y])+tr);//判断是哪个点向上跳
return swap(tl.L,tl.R),(tl+tr).S;//交换第一个点区间左右端值,然后合并
}
}T1;
class Tree2//题意中的第一棵树,树上启发式合并
{
private:
int ee,lnk[N+5],Sz[N+5],son[N+5],ans[N+5];edge e[N<<1];
I void Fill(CI x,CI lst,CI v)//把整棵子树染成v
{
T1.Upt(x,v);for(RI i=lnk[x];i;i=e[i].nxt) e[i].to^lst&&(Fill(e[i].to,x,v),0);
}
I void dfs(CI x,CI lst,CI pos)//遍历
{
if(son[x])//如果有子节点
{
RI i,p;for(i=lnk[x];i;i=e[i].nxt) e[i].to^lst&&//处理轻儿子
(e[i].to^son[x]?(dfs(e[i].to,x,i+1>>1),Fill(e[i].to,x,0),0):p=i+1>>1);//处理完就染白
for(dfs(son[x],x,p),i=lnk[x];i;i=e[i].nxt)//处理重儿子
e[i].to^lst&&e[i].to^son[x]&&(Fill(e[i].to,x,1),0);//然后把其他儿子重新染黑
}T1.Upt(x,1),pos&&(ans[pos]=T1.Qry(x,lst));//把当前点染黑,计算答案
}
public:
I void Clear() {memset(lnk,0,sizeof(lnk)),ee=0;}I void Add(CI x,CI y) {add(x,y);}
I void Init(CI x,CI lst)//初始化求重儿子
{
for(RI i=(Sz[x]=1,son[x]=0,lnk[x]);i;i=e[i].nxt) e[i].to^lst&&
(Init(e[i].to,x),Sz[x]+=Sz[e[i].to],Sz[e[i].to]>Sz[son[x]]&&(son[x]=e[i].to));
}
I void DSU_on_Tree()//树上启发式合并
{
dfs(1,0,0),Fill(1,0,0);//遍历,清空
for(RI i=1;i^n;++i) F.write(ans[i]," \n"[i==n-1]);//输出答案
}
}T2;
int main()
{
RI Tt,i,x,y;F.read(Tt);W(Tt--)
{
T1.Clear(),T2.Clear(),F.read(n);//清空
for(i=1;i^n;++i) F.read(x,y),T2.Add(x,y),T2.Add(y,x);//读入题意中的第一棵树
for(i=1;i^n;++i) F.read(x,y),T1.Add(x,y),T1.Add(y,x);//读入题意中的第二棵树
T1.TreeChainDissection(),T2.Init(1,0),T2.DSU_on_Tree();//求答案
}return F.clear(),0;
}

最新文章

  1. C#学习笔记----栈与堆的知识
  2. linux 下 `dirname $0`
  3. Apache Spark Mesos
  4. 关于WinForm引用WPF窗体
  5. CQRS学习——最小单元的Cqrs(CommandEvent)[其一]
  6. underscorejs-contains学习
  7. java ResultSet 结果集处理 createStatement() 里参数的意义(第一弹)
  8. memcache总结
  9. js 常用插件
  10. Bad Hair Day [POJ3250] [单调栈 或 二分+RMQ]
  11. Lua中的表达式
  12. Apache安装,亲测成功
  13. ESP8266烧录配置
  14. elk-logstash-kibana(三)
  15. sublime插件之px转rem
  16. 互联网推送服务原理:长连接+心跳机制(MQTT协议)
  17. Python random模块random/uniform/randint/choice/getrandbits/shuffle/choice/sample随机函数
  18. windows python监听文件触发脚本
  19. protobuf Protocol Buffers 简介 案例 MD
  20. @ControllerAdvice -- 处理异常示例

热门文章

  1. matlab练习程序(BRIEF描述子)
  2. Android Monkey的用法(一)
  3. 对systemV和systemd的简单理解(服务方面)
  4. vscode相关设置
  5. IT兄弟连 Java语法教程 数据类型1
  6. 前端之jquery1
  7. Java生鲜电商平台-电商会员体系系统的架构设计与源码解析
  8. docker挡板程序实现启动多个实例进程
  9. Kali linux-信息收集-dmitry
  10. Qt之圆角阴影边框