【题解】 2019正睿普及五连测(二)T4——树

题目描述

Description

给定一棵 n 个点的树和树上的 m 条链,请问有多少对链有至少一个公共点。

Input

第一行两个正整数 n,m。 第 2~n 行,每行两个整数 s,t,表示有一条边连接 s 点和 t 点。

接下来 m 行,每行两个整数 a,b,表示有一条链的起点、终点分别为 a,b。(链的起点、 终点可能相同)

Output

一行一个整数,表示答案。

Sample Input

5 3

1 2

1 3

2 4

2 5

4 5

1 4

3 5

Sample Output

3

Hint

对于 30%的数据,$n,m \leq 100$

对于 50%的数据,$n,m \leq 2000$

对于另 20%的数据,读入的 $s,t$ 满足 $t=s+1$

对于 100%的数据,$1 \leq n,m \leq 100000$

时间限制:$1s$ 空间限制:$512MB$


 

题目分析

这一题的关键在于判断两条链是否重合的方法。首先,要分析一下两条链相交的情况。如图所示:

(图A:链 $S1-T1$ 与 $S2-T2$ 有一个交点O)

(图B:链 $S1-T1$ 与 $S2-T2$ 有两个或更多交点)

 

由上图可以得出,两条链相交,当且仅当一条链两端点的LCA在另一条链上。

现在我们需要分类讨论。对于第一种情况,我们只需要计算有多少条链的LCA在某个点上,设$tot_i$为LCA在x点上的链的个数。那么,如果$tot_i>1$,那就说明有多条链经过这一点,也就是说,有$n \times (n - 1) \div 2$对链相交。这个过程用代码写出来就是:

 

for(int i=1;i<=n;i++) ans+=tot[i]*(tot[i]-1)/2;


对于第二种情况,就复杂一些。我们要考虑一条链上有多少个其它链的LCA。我们需要一个前缀和数组$sum$,其递推公式为$sum_v = sum_u + tot_v (v为u的子节点)$,最后要计算每一条链上的和,只需要算出$sum_u + sum_v - 2 \times sum_lca(u,v) (u,v为链的端点)$。这个过程用代码写出来就是:

void solve(int u,int fa)
{
    sum[u]=sum[fa]+tot[u]; //递推
    for(auto &v: g[u]) //遍历子节点
        if(v!=fa)
            solve(v,u);
}

 

for(int i=1;i<=m;i++)    ans+=sum[c[i].u]+sum[c[i].v]-2*sum[c[i].lca];

 

最后,就只需要输出ans的值了。


 

完整代码

不贴代码的题解不是好题解。

 1 #include <bits/stdc++.h>
 2 #define int long long
 3 using namespace std;
 4 int n,m,ans,dep[100005],f[100005][20],sum[100005],tot[100005];
 5 vector<int> g[100005]; //用vector存图,搭配C++11食用效果更佳 
 6 struct Chain
 7 {
 8     int u,v; //链的起点和终点 
 9     int lca; //链的端点的LCA 
10 }c[100005];
11 void dfs(int u,int fa) //LCA预处理 
12 {
13     dep[u]=dep[fa]+1;
14     f[u][0]=fa;
15     for(int i=1;(1<<i)<=dep[u];i++)
16         f[u][i]=f[f[u][i-1]][i-1];
17     for(auto &v: g[u])
18         if(v!=fa)
19             dfs(v,u);
20 }
21 int lca(int a,int b) //求最近公共祖先 
22 {
23     if(dep[a]<dep[b])    swap(a,b);
24     for(int i=17;i>=0;i--)
25     {
26         if(dep[f[a][i]]>=dep[b])
27             a=f[a][i];
28         if(a==b)    return a;
29     }
30     for(int i=17;i>=0;i--)
31         if(f[a][i]!=f[b][i])
32             a=f[a][i],b=f[b][i];
33     return f[a][0];
34 }
35 void solve(int u,int fa) //求前缀和数组 
36 {
37     sum[u]=sum[fa]+tot[u];
38     for(auto &v: g[u])
39         if(v!=fa)
40             solve(v,u);
41 }
42 signed main()
43 {
44     scanf("%lld%lld",&n,&m); //输入数据 
45     for(int i=1;i<n;i++)
46     {
47         int u,v;
48         scanf("%lld%lld",&u,&v);
49         g[u].push_back(v);
50         g[v].push_back(u);
51     }
52     dfs(1,0);
53     for(int i=1;i<=m;i++)
54     {
55         scanf("%lld%lld",&c[i].u,&c[i].v);
56         c[i].lca=lca(c[i].u,c[i].v);
57     }
58     for(int i=1;i<=m;i++)    tot[c[i].lca]++; //记录tot 
59     solve(1,0);
60     for(int i=1;i<=n;i++)    ans+=tot[i]*(tot[i]-1)/2; //情况一计算 
61     for(int i=1;i<=m;i++)    ans+=sum[c[i].u]+sum[c[i].v]-2*sum[c[i].lca]; //情况二计算 
62     printf("%lld",ans);
63     while(1); //不加防抄袭神器的题解也不是好题解…… 
64     return 0;
65 }

 

posted @ 2020-07-28 14:23  ExplodingKonjac  阅读(159)  评论(0编辑  收藏  举报