有一个庞大的家族,共n人。已知这n个人的祖辈关系正好形成树形结构(即父亲向儿子连边)。
在另一个未知的平行宇宙,这n人的祖辈关系仍然是树形结构,但他们相互之间的关系却完全不同了,原来的祖先可能变成了后代,后代变成的同辈……
两个人的亲密度定义为在这两个平行宇宙有多少人一直是他们的公共祖先。
整个家族的亲密度定义为任意两个人亲密度的总和。
Input
第一行一个数n(1<=n<=100000) 接下来n-1行每行两个数x,y表示在第一个平行宇宙x是y的父亲。 接下来n-1行每行两个数x,y表示在第二个平行宇宙x是y的父亲。
Output
一个数,表示整个家族的亲密度。
Input示例
5 1 3 3 5 5 4 4 2 1 2 1 3 3 4 1 5
Output示例
6
思路:
如果某个节点的两个子树中相同的元素有x个,那么对答案的贡献便是x*(x-1)/2
首先我们先对第一棵树处理dfs序和它子树的dfs序的开始和结尾,每个节点的值就是他的dfs序,
接下来我们对第二棵树进行处理dfs序和它子树的dfs序的开始和结尾,每个节点的值是这个节点在第一棵子树中的值,然后根据线性序列建立主席树
查找某个节点在两个子树中的相同元素便相当于转换为主席树中大于等于第一个子树的dfs序的开始小于等于子树的dfs序的结尾的值。
#include<cstdio> #include<algorithm> #include<cstring> #pragma comment(linker, "/STACK:102400000,102400000") using namespace std; const int maxn=100100; int LA[maxn],RA[maxn],LB[maxn],RB[maxn],a[maxn],vis[maxn],val[maxn]; int head[maxn],tot,sz,Root[maxn]; struct node{ int l,r,w; }T[maxn*20]; struct Edge{ int to,next; }e[maxn]; void init(){ memset(head,-1,sizeof(head)); tot=0; } void addedge(int u,int v){ e[tot].to=v; e[tot].next=head[u]; head[u]=tot++; } void dfs(int u,int pre,int flag){ flag==0 ? LA[u]=++sz : RA[u]=++sz; flag==0 ? val[u]=sz : a[sz]=val[u]; for(int i=head[u];i!=-1;i=e[i].next){ int v=e[i].to; if(v==pre) continue; dfs(v,u,flag); } flag==0 ? LB[u]=sz : RB[u]=sz; } void add(int n,int flag){ int u,v,root; memset(vis,0,sizeof(vis)); init(); for(int i=1;i<n;i++){ scanf("%d%d",&u,&v); addedge(u,v); vis[v]=1; } sz=0; for(int i=1;i<=n;i++) if(vis[i]==0) root=i; dfs(root,0,flag); } void update(int &i,int l,int r,int num){ T[++sz]=T[i],i=sz; T[i].w++; if(l==r) return ; int mid=(l+r)>>1; if(num<=mid) update(T[i].l,l,mid,num); else update(T[i].r,mid+1,r,num); } int query(int x,int y,int L,int R,int l,int r){ if(L<=l&&R>=r) return T[y].w-T[x].w; int mid=(l+r)>>1; int ret=0; if(L<=mid) ret+=query(T[x].l,T[y].l,L,R,l,mid); if(R>mid) ret+=query(T[x].r,T[y].r,L,R,mid+1,r); return ret; } int main(){ int n; while(scanf("%d",&n)!=EOF){ add(n,0); add(n,1); Root[0]=0,sz=0; for(int i=1;i<=n;i++){ //dfs序 Root[i]=Root[i-1]; update(Root[i],1,n,a[i]); } long long ans=0; for(int i=1;i<=n;i++){ //以哪个节点为根 int x=query(Root[RA[i]-1],Root[RB[i]],LA[i],LB[i],1,n);//包括了自己这个点 ans+=1LL*(x-1)*(x-2)/2; } printf("%lld\n",ans); } return 0; }