[CF1486F]Pairs of Paths
壹、题目描述 ¶
贰、题解 ¶
先指定一个根,在这里我们就认为根是 \(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\) 也一定是其中一个点。