兩顆\(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; }