#P2788. 第3题-整数权值数
          
                        
                                    
                      
        
              - 
          
          
                      1000ms
            
          
                      Tried: 35
            Accepted: 5
            Difficulty: 8
            
          
          
          
                       所属公司 : 
                              阿里
                                
            
                        
              时间 :2025年4月2日-阿里淘天(算法岗)
                              
                      
          
 
- 
                        算法标签>树          
 
第3题-整数权值数
题解
题面描述
给定一棵有 n 个节点的树,每个节点上都有一个整数权值 wi。小红希望删除若干条边,将这棵树分割成若干个连通块,并要求每个连通块中所有节点权值之和均为偶数。
要求对于每个 k,统计删除 k 条边后得到的 k+1 个连通块都满足条件的方案数(两组边集合不同视为不同方案),若不存在则答案记为 0。
最后答案对 109+7 取模输出。
思路
- 
必要性判断
整棵树的所有节点权值之和为 S=∑i=1nwi。
如果 S 为奇数,则无论如何分割,所有连通块权值之和相加必为奇数(偶数数列求和必定为偶数),因此没有任何一种方案可以使所有连通块的和均为偶数。此时对所有 k 输出 0。 - 
寻找可拆分的边
当 S 为偶数时,我们可以从树中找到若干条边,删除其中任意一条边都能使得以该边为子树的一侧的节点和为偶数。
具体做法是:- 任选一个节点(比如 1)作为根,进行深度优先搜索(DFS)。
 - 对于每个边 u→v(其中 v 为 u 的子节点),计算以 v 为根的子树权值和 Sv。
 - 若 Sv 为偶数,则删除边 u→v 后,该子树就是一个合法的连通块(而剩余部分的和也为 S−Sv,由于 S 为偶数,所以剩余部分也是偶数)。
 
记满足条件的边个数为 m。
 - 
方案计数
每个满足条件的边都是可以独立删除的“候选边”。
当删除 k 条边时,只需从这 m 条候选边中选出任意 k 条即可。
因此答案为 (km)。
当 k>m 时,答案自然为 0。 
C++
#include <iostream>
#include <vector>
using namespace std;
 
const int MOD = 1000000007;
 
int n;
vector<int> weight;
vector<vector<int>> tree;
int m = 0; // 满足条件的候选边数量
 
// DFS 计算子树和的奇偶性,返回值为子树和模 2 的结果
int dfs(int u, int parent) {
    long long sum = weight[u];
    for (int v : tree[u]) {
        if (v == parent) continue;
        int sub = dfs(v, u);
        sum += sub;
        // 如果子树的权值和为偶数,则该边为候选边
        if (sub % 2 == 0) {
            m++;
        }
    }
    return sum;
}
 
// 快速幂求法,计算 a^b mod MOD
long long modExp(long long a, long long b) {
    long long res = 1;
    while(b) {
        if(b & 1) res = res * a % MOD;
        a = a * a % MOD;
        b >>= 1;
    }
    return res;
}
 
int main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
 
    cin >> n;
    weight.resize(n);
    tree.resize(n);
    long long total = 0;
    for (int i = 0; i < n; i++){
        cin >> weight[i];
        total += weight[i];
    }
    for (int i = 0; i < n - 1; i++){
        int u, v;
        cin >> u >> v;
        // 转为 0 基编号
        u--; v--;
        tree[u].push_back(v);
        tree[v].push_back(u);
    }
 
    // 如果整体权值和为奇数,则所有答案均为 0
    if (total % 2 == 1) {
        for (int k = 1; k <= n - 1; k++){
            cout << 0 << (k == n - 1 ? "\n" : " ");
        }
        return 0;
    }
 
    // 以节点 0 作为根,进行 DFS
    dfs(0, -1);
 
    // 预处理阶乘与逆元,用于组合数计算
    int maxN = m; // m 最大不超过 n-1
    vector<long long> fact(maxN + 1), invFact(maxN + 1);
    fact[0] = 1;
    for (int i = 1; i <= maxN; i++){
        fact[i] = fact[i - 1] * i % MOD;
    }
    invFact[maxN] = modExp(fact[maxN], MOD - 2);
    for (int i = maxN - 1; i >= 0; i--){
        invFact[i] = invFact[i + 1] * (i + 1) % MOD;
    }
 
    // 对于每个 k,若 k <= m,则答案为 C(m, k),否则为 0
    for (int k = 1; k <= n - 1; k++){
        if(k > m) {
            cout << 0 << (k == n - 1 ? "\n" : " ");
        } else {
            long long ans = fact[m] * invFact[k] % MOD * invFact[m - k] % MOD;
            cout << ans << (k == n - 1 ? "\n" : " ");
        }
    }
    return 0;
}
Python
import sys
sys.setrecursionlimit(300000)
MOD = 10**9 + 7
# 输入读入
n = int(sys.stdin.readline().strip())
weight = list(map(int, sys.stdin.readline().strip().split()))
tree = [[] for _ in range(n)]
for _ in range(n-1):
    u, v = map(int, sys.stdin.readline().strip().split())
    u -= 1
    v -= 1
    tree[u].append(v)
    tree[v].append(u)
total = sum(weight)
# 如果整体权值和为奇数,则所有方案均为 0
if total % 2 == 1:
    print(" ".join(["0"]*(n-1)))
    sys.exit(0)
m = 0  # 满足条件的候选边数量
# DFS 计算子树权值和的奇偶性
def dfs(u, parent):
    global m
    s = weight[u]
    for v in tree[u]:
        if v == parent:
            continue
        sub = dfs(v, u)
        s += sub
        # 如果子树的权值和为偶数,则该边为候选边
        if sub % 2 == 0:
            m += 1
    return s
dfs(0, -1)
# 预处理阶乘与逆元,用于组合数计算
maxN = m  # m 最大不超过 n-1
fact = [1] * (maxN + 1)
invFact = [1] * (maxN + 1)
for i in range(1, maxN+1):
    fact[i] = fact[i-1] * i % MOD
# 快速幂求法,计算 a^b mod MOD
def modExp(a, b):
    res = 1
    while b:
        if b & 1:
            res = res * a % MOD
        a = a * a % MOD
        b //= 2
    return res
invFact[maxN] = modExp(fact[maxN], MOD-2)
for i in range(maxN-1, -1, -1):
    invFact[i] = invFact[i+1] * (i+1) % MOD
# 对于每个 k (1<=k<=n-1),若 k <= m,则答案为 C(m, k),否则为 0
ans = []
for k in range(1, n):
    if k > m:
        ans.append("0")
    else:
        comb = fact[m] * invFact[k] % MOD * invFact[m-k] % MOD
        ans.append(str(comb))
print(" ".join(ans))
Java
import java.io.*;
import java.util.*;
 
public class Main {
    static final int MOD = 1000000007;
    static int n;
    static int[] weight;
    static ArrayList<Integer>[] tree;
    static int m = 0; // 满足条件的候选边数量
 
    // DFS 计算子树权值和的奇偶性
    static long dfs(int u, int parent) {
        long s = weight[u];
        for (int v : tree[u]) {
            if (v == parent) continue;
            long sub = dfs(v, u);
            s += sub;
            // 如果子树的权值和为偶数,则该边为候选边
            if (sub % 2 == 0) {
                m++;
            }
        }
        return s;
    }
 
    // 快速幂求法,计算 a^b mod MOD
    static long modExp(long a, long b) {
        long res = 1;
        while(b > 0) {
            if ((b & 1) == 1) {
                res = res * a % MOD;
            }
            a = a * a % MOD;
            b >>= 1;
        }
        return res;
    }
 
    public static void main(String[] args) throws IOException{
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        n = Integer.parseInt(br.readLine().trim());
        weight = new int[n];
        tree = new ArrayList[n];
        String[] ws = br.readLine().trim().split("\\s+");
        long total = 0;
        for (int i = 0; i < n; i++){
            weight[i] = Integer.parseInt(ws[i]);
            total += weight[i];
            tree[i] = new ArrayList<>();
        }
 
        for (int i = 0; i < n - 1; i++){
            String[] parts = br.readLine().trim().split("\\s+");
            int u = Integer.parseInt(parts[0]) - 1;
            int v = Integer.parseInt(parts[1]) - 1;
            tree[u].add(v);
            tree[v].add(u);
        }
 
        // 如果整体权值和为奇数,则所有方案均为 0
        if (total % 2 == 1) {
            StringBuilder sb = new StringBuilder();
            for (int k = 1; k <= n - 1; k++){
                sb.append("0").append(k == n - 1 ? "\n" : " ");
            }
            System.out.print(sb.toString());
            return;
        }
 
        // 从节点 0 作为根进行 DFS
        dfs(0, -1);
 
        // 预处理阶乘与逆元,用于组合数计算
        int maxN = m;  // m 最大不超过 n-1
        long[] fact = new long[maxN + 1];
        long[] invFact = new long[maxN + 1];
        fact[0] = 1;
        for (int i = 1; i <= maxN; i++){
            fact[i] = fact[i - 1] * i % MOD;
        }
        invFact[maxN] = modExp(fact[maxN], MOD - 2);
        for (int i = maxN - 1; i >= 0; i--){
            invFact[i] = invFact[i + 1] * (i + 1) % MOD;
        }
 
        // 对于每个 k (1<=k<=n-1),若 k <= m,则答案为 C(m, k),否则为 0
        StringBuilder sb = new StringBuilder();
        for (int k = 1; k <= n - 1; k++){
            if (k > m) {
                sb.append("0");
            } else {
                long comb = fact[m] * invFact[k] % MOD * invFact[m - k] % MOD;
                sb.append(comb);
            }
            if (k != n - 1) sb.append(" ");
        }
        System.out.println(sb.toString());
    }
}
        题目内容
小红有一棵树,每个节点上都有一个整数权值。她希望通过删除若干条边,将这棵树分割为若干个连通块,使得每个连通块中所有节点的权值之和都是偶数。
请你求出,对于每个 k(1≦k≦n−1) ,删除 k 条边后得到的 k+1 个连通块满足条件的方案数。如果不存在满足条件的方案,对应的答案记为 0 。
注意:两种删除边的方案若删除的边集合不同,则视为不同的方案。
输入描述
第一行给出一个整数 n ,表示树的节点数。
第二行给出 n 个整数 w1,w2,...,wn ,其中 wi 表示节点i的权值。
接下来 n−1 行,每行包含两个整数 u 与 v(1≦u,v≦n,u=v) ,表示节点 u 与节点 v 之间有一条边,保证给定的图构成一棵树。
2≤n≤105
1≤wi≤109
1≤u,v≦n
输出描述
输出 n−1 个数,第 i 个数表示删除 i 条边后满足条件的方案数。
由于答案可能非常大,请对答案取模 109+7 后输出。
样例1
输入
5
1 2 3 4 4
1 2
1 3
2 4
2 5
输出
3 3 1 0
说明
当 k=1 时,删除方案有 {(1,2)},{(2,4)},{(2,5)},共 3 种。
当 k=2 时,删除方案有
{(1,2),(2,4)},{(1,2),(2,5)},{(2,5),(2,4)},共 3 种。
当 k=3 时,删除方案有{(1,2),(2,4),(2,5)},共 1 种。