题目链接
Leetcode.2867 统计树中的合法路径数目
rating : 2428
题目描述
给你一棵 n n n 个节点的无向树,节点编号为 1 1 1 到 n n n 。给你一个整数 n n n 和一个长度为 n − 1 n - 1 n−1 的二维整数数组 e d g e s edges edges ,其中 e d g e s [ i ] = [ u i , v i ] edges[i] = [u_i, v_i] edges[i]=[ui,vi] 表示节点 u i u_i ui 和 v i v_i vi 在树中有一条边。
请你返回树中的 合法路径数目 。
如果在节点 a a a 到节点 b b b 之间 恰好有一个 节点的编号是质数,那么我们称路径 ( a , b ) (a, b) (a,b) 是 合法的 。
注意:
- 路径 ( a , b ) (a, b) (a,b) 指的是一条从节点 a a a 开始到节点 b b b 结束的一个节点序列,序列中的节点 互不相同 ,且相邻节点之间在树上有一条边。
- 路径 ( a , b ) (a, b) (a,b) 和路径 ( b , a ) (b, a) (b,a) 视为 同一条 路径,且只计入答案 一次 。
示例 1:
输入:n = 5, edges = [[1,2],[1,3],[2,4],[2,5]]
输出:4
解释:恰好有一个质数编号的节点路径有:
- (1, 2) 因为路径 1 到 2 只包含一个质数 2 。
- (1, 3) 因为路径 1 到 3 只包含一个质数 3 。
- (1, 4) 因为路径 1 到 4 只包含一个质数 2 。
- (2, 4) 因为路径 2 到 4 只包含一个质数 2 。
只有 4 条合法路径。
示例 2:
输入:n = 6, edges = [[1,2],[1,3],[2,4],[3,5],[3,6]]
输出:6
解释:恰好有一个质数编号的节点路径有:
- (1, 2) 因为路径 1 到 2 只包含一个质数 2 。
- (1, 3) 因为路径 1 到 3 只包含一个质数 3 。
- (1, 4) 因为路径 1 到 4 只包含一个质数 2 。
- (1, 6) 因为路径 1 到 6 只包含一个质数 3 。
- (2, 4) 因为路径 2 到 4 只包含一个质数 2 。
- (3, 6) 因为路径 3 到 6 只包含一个质数 3 。
只有 6 条合法路径。
提示:
- 1 ≤ n ≤ 1 0 5 1 \leq n \leq 10^5 1≤n≤105
- e d g e s . l e n g t h = n − 1 edges.length = n - 1 edges.length=n−1
- e d g e s [ i ] . l e n g t h = 2 edges[i].length =2 edges[i].length=2
- 1 ≤ u i , v i ≤ n 1 \leq u_i, v_i \leq n 1≤ui,vi≤n
- 输入保证 e d g e s edges edges 形成一棵合法的树。
解法:筛质数 + dfs
我们先将 [ 1 , 1 0 5 ] [1,10^5] [1,105] 内的质数筛出来,用 n p np np 记录。如果 n p [ x ] = t r u e np[x] = true np[x]=true,说明 x x x 不是质数;否则是质数。
我们可以从 质数点 x x x 出发,开始 dfs,访问非质数点,因为一条 合法路径 只存在一个质数点。
通过上述操作,可以将 x x x 连接的几个 非质数连通块 计算出来。
假设如上图,通过 x x x 分割出了三个 非质数连通块 ,每个连通块中的节点数量分别为 2 , 3 , 4 2,3,4 2,3,4。
我们现在来计算 合法路径数量(从左到右计算,保证不重不漏):
- 1 1 1号连通块 通过 x x x 和 2 2 2号联通块 组成的路径数为 2 × 3 = 6 2\times 3 = 6 2×3=6;
- 1 1 1号连通块, 2 2 2号连通块 通过 x x x 和 3 3 3号连通块 组成的路径数为 ( 2 + 3 ) × 4 = 20 (2 + 3) \times 4 = 20 (2+3)×4=20;
- 以及所有连通块的节点数量 9 9 9
上图通过 x x x 的合法路径条数为 6 + 20 + 9 = 35 6 + 20 + 9 = 35 6+20+9=35。
我们可以用一个数组 s z sz sz,记录下已经计算过的连通块数量。假设 1 1 1 号连通块中的两个节点分别为 i , j i , j i,j,那么 s z [ i ] = s z [ j ] = 2 sz[i] = sz[j] = 2 sz[i]=sz[j]=2。
如果下一次有另外一个 质数点 y y y 和这个 1 1 1号连通块 相连,那么我们就可以直接计算,不用再次遍历记录连通块节点的数量。
时间复杂度: O ( n ) O(n) O(n)
C++代码:
using LL = long long;
const int MX = 1e5;
bool np[MX + 1];
auto init = []() ->int{
np[1] = true;
for(int i = 2;i * i <= MX;i++){
if(!np[i]){
for(int j = i * i;j <= MX;j += i) np[j] = true;
}
}
return 0;
}();
class Solution {
public:
long long countPaths(int n, vector<vector<int>>& edges) {
vector<vector<int>> g(n + 1);
for(auto &e:edges){
auto a = e[0] , b = e[1];
g[a].push_back(b);
g[b].push_back(a);
}
vector<int> sz(n + 1);
vector<int> nodes;
function<void(int,int)> dfs = [&](int x,int fa) ->void{
nodes.push_back(x);
for(auto y:g[x]){
if(y == fa || !np[y]) continue;
dfs(y,x);
}
};
LL ans = 0;
for(int x = 1;x <= n;x++){
if(np[x]) continue; //合数就跳过 , 是以质数为起点dfs
LL sum = 0;
for(auto y : g[x]){
//接着要求的是合数的连通块
if(!np[y]) continue;
//如果这个连通块没有被计算过,就计算
if(sz[y] == 0){
nodes.clear();
dfs(y,-1);
for(auto node:nodes) sz[node] = nodes.size();
}
ans += sum * sz[y];
sum += sz[y];
}
ans += sum;
}
return ans;
}
};