两颗\(n\)节点的树,不相同,问多少点对\((u,v)\)在两棵树上均知足路径\(v\)在\(u\)子树中数组
\(n\le 10^5\)spa
暴力:code
\(n^2\)暴力枚举点对用\(dfs\)序\(O(1)\)判断是非知足条件,或者用欧拉序\(O(1)\)求lcaget
正解:io
先跑第一棵树,求出其\(dfs\)序,记录下节点\(i\)的\(dfs\)序开始与结束位置。class
而后跑第二棵树,维护一个下标为\(dfs\)序的树状数组,每次第一次遍历到节点\(i\)时,咱们统计在当前节点的\(dfs\)序以前(即知足在第一棵树上节点\(i\)在\(j\)的子树中)且在当前这第二棵树上已经遍历过的节点(即知足在第二棵树上节点\(i\)在\(j\)的子树中)的个数,加入到答案。这个过程至关于统计每一个\((u,v)\)中的\(v\)。cli
具体看代码实现吧。遍历
#include <cstdio> #define MAXN 100001 using namespace std; inline int read(){ char ch=getchar();int s=0; while(ch<'0'||ch>'9') ch=getchar(); while(ch>='0'&&ch<='9') s=s*10+(ch^'0'), ch=getchar(); return s; } int n; int tre[MAXN]; void add(int x, int val){ while(x<=n) tre[x]+=val,x+=x&(-x); } int get_sum(int x){ int res=0; while(x>0) res+=tre[x],x-=x&(-x); return res; } int dfn[MAXN],dfn_out[MAXN],cnt; int ans[MAXN]; namespace tre1 { int head[MAXN],nxt[MAXN*2],vv[MAXN*2],tot; inline void add_edge(int u, int v){ vv[++tot]=v; nxt[tot]=head[u]; head[u]=tot; } void dfs(int u, int fa){ dfn[u]=++cnt; for(int i=head[u];i;i=nxt[i]){ int v=vv[i]; if(v==fa) continue; dfs(v, u); } dfn_out[u]=cnt; } } namespace tre2 { int head[MAXN],nxt[MAXN*2],vv[MAXN*2],tot; inline void add_edge(int u, int v){ vv[++tot]=v; nxt[tot]=head[u]; head[u]=tot; } void solve(int u, int fa){ ans[u]=get_sum(dfn[u]-1); add(dfn[u], 1); add(dfn_out[u], -1); for(int i=head[u];i;i=nxt[i]){ int v=vv[i]; if(v==fa) continue; solve(v, u); } add(dfn[u], -1); add(dfn_out[u], 1); } } int main(){ //freopen("climb.in", "r", stdin); //freopen("climb.out", "w", stdout); n=read(); for(int i=1;i<n;++i){ int u=read(),v=read(); tre1::add_edge(u, v); tre1::add_edge(v, u); } for(int i=1;i<n;++i){ int u=read(),v=read(); tre2::add_edge(u, v); tre2::add_edge(v, u); } tre1::dfs(1, 1); tre2::solve(1, 1); long long sum=0; for(int i=1;i<=n;++i) sum+=ans[i]; printf("%lld", sum); return 0; }