51nod 1681 公共祖先

题目描述:戳这里
题解:
这个题目有一个非常好的技巧。我们要求的其实是某一个点在两颗树中的子树中同时含有的点的数量。我们发现这个东西很难求,因为这个东西在两个子树中都是散乱无序的。那么我们如果把一颗子树中的量变得有一定的关系可循,那么接下来只需要关心另一颗树就好了。那么我们不妨把第一颗树重新标一个号,编号就为原树的dfs序。那么第一颗子树中的点的编号都会变成连续的。接下来只要到第二颗子树中查询这个连续的区间中有多少点在某个子树中出现的次数就好了。这个可以用主席树来维护。

代码如下:

#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int maxn=100005;
int n,tot,cnt,lnk[maxn],son[2*maxn],nxt[2*maxn],id[maxn],r[maxn],ll[maxn],rr[maxn],inn[maxn],inn1[maxn];
int lson[18*maxn],rson[18*maxn],c[18*maxn],root[maxn],que[maxn];
LL ans;
void add(int x,int y){son[++tot]=y,nxt[tot]=lnk[x],lnk[x]=tot;}
void dfs(int x){
	id[x]=++cnt;
	for (int j=lnk[x];j;j=nxt[j]) dfs(son[j]);
	r[id[x]]=cnt;
}
void DFS(int x){
	ll[x]=++cnt; que[cnt]=x;
	for (int j=lnk[x];j;j=nxt[j]) DFS(son[j]);
	rr[x]=cnt;
}
void upd(int x,int y){lson[x]=lson[y],rson[x]=rson[y],c[x]=c[y];}
void insert(int &x,int y,int l,int r,int num){
	x=++tot; upd(x,y);
	if (l==r) {c[x]++; return;}
	int mid=(l+r)>>1;
	if (num<=mid) insert(lson[x],lson[y],l,mid,num);
	else insert(rson[x],rson[y],mid+1,r,num);
	c[x]=c[lson[x]]+c[rson[x]];
}
int query(int x,int y,int ll,int rr,int l,int r){
	if (ll<=l&&r<=rr) return c[y]-c[x];
	int mid=(l+r)>>1,now=0;
	if (ll<=mid) now+=query(lson[x],lson[y],ll,rr,l,mid);
	if (rr>mid) now+=query(rson[x],rson[y],ll,rr,mid+1,r);
	return now;
}
LL doit(int x){return 1ll*x*(x-1)/2;}
int main(){
	scanf("%d",&n);
	for (int i=1;i<n;i++){
		int x,y; scanf("%d%d",&x,&y);
		add(x,y); inn[y]++;
	}
	for (int i=1;i<=n;i++) if (inn[i]==0) {dfs(i); break;}
	cnt=0; tot=0;
	memset(lnk,0,sizeof(lnk));
	for (int i=1;i<n;i++){
		int x,y; scanf("%d%d",&x,&y);
		add(id[x],id[y]); inn1[id[y]]++;
	}
	for (int i=1;i<=n;i++) if (inn1[i]==0) {DFS(i); break;}
	tot=0;
	for (int i=1;i<=n;i++)
	insert(root[i],root[i-1],1,n,que[i]);
	for (int i=1;i<=n;i++){
	 	int x=id[i];
	 	ans+=doit(query(root[ll[x]],root[rr[x]],x+1,r[x],1,n));
	}
	printf("%lld\n",ans);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值