[CF1486F]Pairs of Paths

壹、题目描述 ¶

传送门 to Luogu.

贰、题解 ¶

先指定一个根,在这里我们就认为根是 \(1\)(不然 \(\rm LCA\) 没有定义)

考察两条路径 \(\lang x,y\rang,\lang u,v\rang\) 只有一个交点,他们交点的性质 —— 一定是某一对点的 \(\rm LCA\),证明可以考虑反证法:

如果交点并不是两个点对的 \(\rm LCA\),那么只有两种情况:

  • 这个交点有两条可以往上走的边,但是这是树,不可能存在一个点有俩父亲;
  • 这个交点往上走还是这俩路径的交点,这就不满足我们对于 “相交” 的定义;

只是证明一下这个,剩下的就宅一宅大佬的了......只不过做了一些批注......

第一种情况:两条路径的 \(\rm LCA\) 相同:

容斥一下即可,总数 \({siz(siz-1)\over 2}\)[1],减去在一个子树内相交的个数[2],再加上两个子树都相交的个数。这里我们可以开一个桶解决,排序线性的话可以在 \(\mathcal O(n)\) 时间内解决。

第二种情况:另一条路径的 \(\rm LCA\) 深度小:

我们对所有路径按 \(\rm LCA\) 深度从小到大排序。将 \(\rm LCA\) 深度小于当前 \(\rm LCA\) 深度的点的 \(cnt\) 加上一。这样当前 \(\rm LCA\) 对答案的贡献也可以容斥一下算出来。具体一点,就是以 \(\rm LCA\) 为根整个子树的 \(cnt\) 之和,减去路径一端 \(u\) 对应的子树的和,再减去路径另一端 \(v\) 对应的子树之和。[3]

这可以用 \(\rm dfn\) 序配合树状数组在 \(\mathcal O(n\log n)\) 的时间内解决。

注意一下如果路径是一条单链或者一个点需要特别处理边界情况。

叁、参考代码 ¶

const int maxn=3e5;
const int logn=18;

vector<int>g[maxn+5];

int n, m;

namespace bit{
	int c[maxn+5];
	#define lowbit(i) ((i)&(-i))
	inline int modify(int i){
		for(; i<=n; i+=lowbit(i)) ++c[i];
	}
	inline int query(int i){
		int ret=0;
		for(; i; i-=lowbit(i)) ret+=c[i];
		return ret;
	}
	inline int query(int l, int r){
		return query(r)-query(l-1);
	}
}

inline void add_edge(int u, int v){
	g[u].push_back(v);
	g[v].push_back(u);
}

inline void input(){
	n=readin(1);
	int u, v;
	rep(i, 1, n-1){
		u=readin(1), v=readin(1);
		add_edge(u, v);
	}
}

int tp[maxn+5][logn+5], dep[maxn+5], siz[maxn+5], dfn[maxn+5], timer;
void dfs(int u, int par){
	dfn[u]=++timer;
	tp[u][0]=par, dep[u]=dep[par]+1, siz[u]=1;
	rep(j, 1, logn) tp[u][j]=tp[tp[u][j-1]][j-1];
	for(int v: g[u]) if(v!=par)
		dfs(v, u), siz[u]+=siz[v];
}

inline int climb(int u, int up){
	drep(j, logn, 0) if(dep[tp[u][j]]>=up)
		u=tp[u][j];
	return u;
}

inline int getlca(int u, int v){
	if(dep[u]<dep[v]) swap(u, v);
	u=climb(u, dep[v]);
	if(u==v) return u;
	drep(j, logn, 0) if(tp[u][j]!=tp[v][j])
		u=tp[u][j], v=tp[v][j];
	return tp[u][0];
}

struct path{
	int lca, u, v;
	path(){}
	path(int L, int U, int V): lca(L), u(U), v(V){}
	inline int operator <(const path rhs) const{
		return lca<rhs.lca;
	}
}p[maxn+5];
int x[maxn+5], y[maxn+5];

inline void getpath(){
	int u, v, l, preu, prev;
	m=readin(1);
	rep(i, 1, m){
		u=x[i]=readin(1), v=y[i]=readin(1);
		l=getlca(u, v);
		preu=climb(u, dep[l]+1);
		prev=climb(v, dep[l]+1);
		p[i]=path(l, u==l? -1: preu, v==l? -1: prev);
		// used for hash
		if(p[i].u>p[i].v) swap(p[i].u, p[i].v);
	}
}

map<pii, int>w;
ll ans; int c[maxn+5];
inline void solve(int l, int r){
	rep(i, l, r){
		// inclusion-exclusion
		ans+=i-l;
		if(~p[i].u) ans-=c[p[i].u], ++c[p[i].u];
		if(~p[i].v) ans-=c[p[i].v], ++c[p[i].v];
		if(~p[i].u && ~p[i].v)
			ans+=w[mp(p[i].u, p[i].v)], ++w[mp(p[i].u, p[i].v)];
	}
	// clear
	rep(i, l, r){
		if(~p[i].u) --c[p[i].u];
		if(~p[i].v) --c[p[i].v];
	}
	w.clear();
}

inline void calc1(){
	sort(p+1, p+m+1); // sort p by lca, classify the same lca
	int pre=1;
	rep(i, 1, m) if(p[i].lca!=p[i-1].lca)
		solve(pre, i-1), pre=i;
	solve(pre, m); // pay attention!
}

inline int cmp(const path lhs, const path rhs){
	return dep[lhs.lca]<dep[rhs.lca];
}
inline void maintain(int l, int r){
	rep(i, l, r){
		bit::modify(dfn[p[i].u]);
		bit::modify(dfn[p[i].v]);
	}
}
inline void calc2(){
	rep(i, 1, m) p[i]=path(getlca(x[i], y[i]), x[i], y[i]);
	sort(p+1, p+m+1, cmp); // sort p by depth of lca, from shallow to deep
	int pre=1, cur;
	rep(i, 1, m){
		// solve the same depth at once
		if(dep[p[i].lca]!=dep[p[i-1].lca])
			maintain(pre, i-1), pre=i;
		cur=p[i].lca;
		ans+=bit::query(dfn[cur], dfn[cur]+siz[cur]-1);
		if(p[i].u!=p[i].lca){
			cur=climb(p[i].u, dep[p[i].lca]+1);
			ans-=bit::query(dfn[cur], dfn[cur]+siz[cur]-1);
		}
		if(p[i].v!=p[i].lca){
			cur=climb(p[i].v, dep[p[i].lca]+1);
			ans-=bit::query(dfn[cur], dfn[cur]+siz[cur]-1);
		}
	}
}

signed main(){
	input();
	dfs(1, 0);
	getpath();
	calc1();
	calc2();
	printf("%lld\n", ans);
	return 0;
}

肆、用到 の Trick

两条路径相交,交点一定在某一对的 \(\rm LCA\) 上。

另外,如果不仅仅是相交,而是有一段重复的,\(\rm LCA\) 也一定是其中一个点。


  1. 这个 \(siz\) 并不是指子树大小,而是拥有同一 \(\rm LCA\) 的路径个数。 ↩︎

  2. 这个相交的个数有一个特殊的处理,对于路径 \(\lang u,v\rang\),它们一定会经过 \(u\)\(\rm LCA\) 存在的子树的树根,我们认为两个同类的路径不交,就是他们对应的子树的树根不同。 ↩︎

  3. 为什么不用考虑减去重复的情况?因为并不是同一类,所以不可能出现同时在 \(u,v\) 相交的情况。 ↩︎

posted @ 2021-05-15 09:21  Arextre  阅读(44)  评论(0编辑  收藏  举报