#P2956. 第3题-好联通块
          
                        
                                    
                      
        
              - 
          
          
                      1000ms
            
          
                      Tried: 15
            Accepted: 3
            Difficulty: 10
            
          
          
          
                       所属公司 : 
                              阿里
                                
            
                        
              时间 :2025年5月12日-阿里国际(开发岗)
                              
                      
          
 
- 
                        算法标签>动态规划          
 
第3题-好联通块
题解
题目描述
给定一棵节点数为 n 的树,节点编号为 1,2,…,n,每个节点的权值为 ai。我们定义一个连通块为“好连通块”,当且仅当该连通块中所有节点的权值的乘积的末尾至少有一个零。也就是说,乘积中包含因子 2 和 5 各至少一个。
求树上所有“好连通块”的个数,结果对 109+7 取模。
连通块定义
对于树上的任意一个点集 S,如果点集中的任意两点 u,v 满足“从 u 到 v 的简单路径上的所有点都在 S 中”,则称 S 是一个连通块。单个节点也算一个连通块。
思路
1. 状态表示
每个节点的权值可以被分解成因子 2 和 5。我们通过计算每个节点中 2 和 5 的因子数量,来判断该节点在乘积中是否会导致尾数为零。
- 若一个数可被 2 整除,则该节点贡献一个因子 2。
 - 若一个数可被 5 整除,则该节点贡献一个因子 5。
 
因此,我们可以用一个二进制状态来表示每个节点的因子信息:
00: 既不包含因子 2 也不包含因子 5;01: 只包含因子 5;10: 只包含因子 2;11: 同时包含因子 2 和 5。
2. 动态规划
在树的每个节点上,我们使用一个动态规划数组 dp[u][state] 来记录以节点 u 为根的子树中,所有连通块的状态信息,其中 state 表示该连通块包含的因子状态。dp[u][state] 记录了所有从根节点 u 出发的连通块,其状态为 state 的数量。
3. DFS遍历树
通过深度优先搜索(DFS)遍历树,处理每一个节点及其子树:
- 在第一次访问节点时,计算节点的因子状态并初始化 
dp[u]。 - 在回溯时,将当前节点的子树状态与其父节点的状态合并,更新动态规划表。
 - 最终我们需要统计所有包含因子 2 和 5 的连通块的数量,即 
dp[u][3]。 
4. 重心分解与状态传递
我们采用栈模拟DFS,避免递归深度过深的限制。在每次递归时,将当前节点的状态与其子树状态进行合并。
5. 时间复杂度
此算法的时间复杂度为 O(n),其中 n 是树的节点数。每个节点的状态更新操作只会执行常数次,整个算法可以在 O(n) 时间内完成。
C++
#include <bits/stdc++.h>
using namespace std;
const int MOD = 1e9 + 7;
const int MAXN = 1e5 + 5;
vector<int> adj[MAXN];
array<long long, 4> dp[MAXN];
int a[MAXN];
// 计算num中f因子的个数
int get_factor(int num, int f) {
    int cnt = 0;
    while (num % f == 0) {
        cnt++;
        num /= f;
    }
    return cnt;
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    int n;
    cin >> n;
    // 输入每个节点的权值
    for (int i = 1; i <= n; ++i)
        cin >> a[i];
    
    // 输入树的边
    for (int i = 0; i < n - 1; ++i) {
        int u, v;
        cin >> u >> v;
        adj[u].push_back(v);
        adj[v].push_back(u);
    }
    stack<tuple<int, int, bool>> st;
    st.push({1, -1, false});
    long long ans = 0;
    // 栈模拟DFS
    while (!st.empty()) {
        auto [u, p, vis] = st.top();
        st.pop();
        if (!vis) {
            // 计算当前节点的因子状态
            int cnt2 = get_factor(a[u], 2);
            int cnt5 = get_factor(a[u], 5);
            bool has2 = cnt2 >= 1;
            bool has5 = cnt5 >= 1;
            int state = (has2 << 1) | has5;
            dp[u].fill(0);
            dp[u][state] = 1;
            // 先标记当前节点处理完,再进入子节点
            st.push({u, p, true});
            for (int v : adj[u]) {
                if (v != p) {
                    st.push({v, u, false});
                }
            }
        } else {
            // 回溯时合并子树的dp状态
            for (int v : adj[u]) {
                if (v != p) {
                    array<long long, 4> tmp = {0};
                    for (int i = 0; i < 4; ++i) {
                        for (int j = 0; j < 4; ++j) {
                            int k = i | j;
                            tmp[k] = (tmp[k] + dp[u][i] * dp[v][j]) % MOD;
                        }
                    }
                    for (int k = 0; k < 4; ++k) {
                        dp[u][k] = (dp[u][k] + tmp[k]) % MOD;
                    }
                }
            }
            // 统计好连通块的数量
            ans = (ans + dp[u][3]) % MOD;
        }
    }
    cout << ans << "\n";
    return 0;
}
Python
import sys
from collections import deque
sys.setrecursionlimit(10**7)
MOD = 10**9 + 7
def get_factor(num, f):
    cnt = 0
    while num % f == 0:
        cnt += 1
        num //= f
    return cnt
def main():
    data = sys.stdin.read().split()
    it = iter(data)
    n = int(next(it))
    a = [0] * (n + 1)
    for i in range(1, n + 1):
        a[i] = int(next(it))
    adj = [[] for _ in range(n + 1)]
    for _ in range(n - 1):
        u = int(next(it)); v = int(next(it))
        adj[u].append(v)
        adj[v].append(u)
    dp = [[0] * 4 for _ in range(n + 1)]
    ans = 0
    # stack elements: (node, parent, visited_flag)
    stack = [(1, -1, False)]
    while stack:
        u, p, vis = stack.pop()
        if not vis:
            # compute current node state
            cnt2 = get_factor(a[u], 2)
            cnt5 = get_factor(a[u], 5)
            has2 = 1 if cnt2 >= 1 else 0
            has5 = 1 if cnt5 >= 1 else 0
            state = (has2 << 1) | has5
            # reset dp[u]
            dp[u] = [0] * 4
            dp[u][state] = 1
            # push for post-order
            stack.append((u, p, True))
            # push children
            for v in adj[u]:
                if v == p:
                    continue
                stack.append((v, u, False))
        else:
            # merge children's dp
            for v in adj[u]:
                if v == p:
                    continue
                tmp = [0] * 4
                for i in range(4):
                    if dp[u][i] == 0:
                        continue
                    for j in range(4):
                        if dp[v][j] == 0:
                            continue
                        k = i | j
                        tmp[k] = (tmp[k] + dp[u][i] * dp[v][j]) % MOD
                for k in range(4):
                    dp[u][k] = (dp[u][k] + tmp[k]) % MOD
            # add to answer
            ans = (ans + dp[u][3]) % MOD
    print(ans)
if __name__ == '__main__':
    main()
Java
import java.util.*;
public class Main {
    static final int MOD = 1000000007;
    static final int MAXN = 100005;
    static List<Integer>[] adj = new ArrayList[MAXN];
    static long[][] dp = new long[MAXN][4];
    static int[] a = new int[MAXN];
    // 计算num中f因子的个数
    static int getFactor(int num, int f) {
        int cnt = 0;
        while (num % f == 0) {
            cnt++;
            num /= f;
        }
        return cnt;
    }
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        
        for (int i = 1; i <= n; i++) {
            a[i] = sc.nextInt();
            adj[i] = new ArrayList<>();
        }
        for (int i = 0; i < n - 1; i++) {
            int u = sc.nextInt();
            int v = sc.nextInt();
            adj[u].add(v);
            adj[v].add(u);
        }
        // 栈模拟DFS
        Stack<int[]> stack = new Stack<>();
        stack.push(new int[] { 1, -1, 0 });  // 初始节点为1,父节点为-1,未访问
        long ans = 0;
        while (!stack.isEmpty()) {
            int[] node = stack.pop();
            int u = node[0], p = node[1], vis = node[2];
            if (vis == 0) {
                int cnt2 = getFactor(a[u], 2);
                int cnt5 = getFactor(a[u], 5);
                boolean has2 = cnt2 >= 1;
                boolean has5 = cnt5 >= 1;
                int state = (has2 ? 2 : 0) | (has5 ? 1 : 0);
                Arrays.fill(dp[u], 0);
                dp[u][state] = 1;
                stack.push(new int[] { u, p, 1 });
                for (int v : adj[u]) {
                    if (v != p) {
                        stack.push(new int[] { v, u, 0 });
                    }
                }
            } else {
                for (int v : adj[u]) {
                    if (v != p) {
                        long[] tmp = new long[4];
                        for (int i = 0; i < 4; i++) {
                            for (int j = 0; j < 4; j++) {
                                int k = i | j;
                                tmp[k] = (tmp[k] + dp[u][i] * dp[v][j]) % MOD;
                            }
                        }
                        for (int k = 0; k < 4; k++) {
                            dp[u][k] = (dp[u][k] + tmp[k]) % MOD;
                        }
                    }
                }
                ans = (ans + dp[u][3]) % MOD;
            }
        }
        System.out.println(ans);
    }
}
        题目内容
小红获得一棵节点数为 n 的树,节点编号为 1,2,…,n ,其中第 i 个节点的权值为 ai 定义一个连通块为"好连通块":该连通块中所有节点的点权乘积尾数存在0 。
求好连通块的个数,结果对 109+7 取模。
对于树上的任意一个点集 S,如果点集中的仼意两点 u,v 满足" u 到 v 的简单路径上的所有点都在点集中",则称 S 是一个连通块。特别地,单独的点也构成一个连通块。
输入描述
第一行输入一个整数 n(1≤n≤105) ,表示树的节点数。
第二行输入 n 个整数 a1,a2,…,an(1≤ai≤109) ,表示每个节点的权值。
接下来 n−1 行,每行输入两个整数 u 和 v ,表示树上的一条边 (u,v) 。
输出描述
输出一个整数,表示好连通块的个数,结果对 109+7 取模。
样例1
输入
3
2 5 10
1 2
1 3
输出
4
说明
连通块 {3} 是好连通块,a3=10 。
连通块 {1,2} 是好连通块,a1×a2=10 。
连通块 {1,3} 是好连通块,a1×a3=20 。
连通块 {1,2,3} 是好连通块,a1×a2×a3=100 。
总共 4 个。