You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

3.9 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

51nod 1588 幸运树

【知识点】树形dp统计树上方案数

一、题目描述

定义幸运数字只由47组成,比如4747。 定义幸运数字只由47组成,比如4747

给一棵树,要我们找到三元组(i,j,k),两两之间的路径中必须要有一条由幸运数字组成的边。问,存在多少组这样的三元组。

二、解题思路

幸运数字好处理,check一下。关键是怎么找出贡献。

统计树上方案数,一般先固定一个点,比如i,然后再找另外两个点jk,算出i这个点对应的贡献。

  • s[i]为以i为根节点的子树中,有几个点到i的路径中存在幸运数字
  • f[i]为以i为根节点的子树外,有几个点到i的路径中存在幸运数字

这样,我们的 jk 的选择就可以在f中选择,或者g中选择,或者在fg中选择。

i的贡献为

\large s[i]*(s[i]-1)+f[i]*(f[i]-1)+s[i]*f[i]*2

解释:

  • s[i]*(s[i]-1) j,k都在以i为根节点的子树中
  • f[i]*(f[i]-1) j,k都在以i为根节点的子树外
  • s[i]*f[i] ji为根节点的子树中,ki为根节点的子树外
  • f[i]*s[i] ki为根节点的子树中,ji为根节点的子树外

然后就是处理fg

dfs过程中

这些式子也还是都是满满的套路啦

  • 如果uv的边是幸运数字,则s[u]+=sz[v],否则s[u]+=s[v]

  • 如果vu的边是幸运数字,则f[v]+=sz[1]-sz[v],否则f[v]+=f[u]+s[u]s[v]

所以要先dfs一遍预处理ssz,然后dfs一遍处理f,最后统计方案。

三、实现代码

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;
const int N = 1e6 + 10, M = N << 1;

// 链式前向星
int e[M], h[N], idx, w[M], ne[M];
void add(int a, int b, int c = 0) {
    e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++;
}

LL s[N], f[N];
int sz[N];
int st[N];

void dfs1(int u) {
    st[u] = 1;
    sz[u] = 1; // u节点自己加入
    for (int i = h[u]; ~i; i = ne[i]) {
        int v = e[i];
        if (st[v]) continue;
        // 先执行噢
        dfs1(v);
        // 统计u子树中节点数量
        sz[u] += sz[v];
        // 幸运边
        if (w[i])
            s[u] += sz[v]; // v子树中所有节点都可以为s[u]贡献力量
        else
            s[u] += s[v]; // v这个点是指望不上的它的子树中的贡献力量
    }
}

void dfs2(int u) {
    st[u] = 1;
    for (int i = h[u]; ~i; i = ne[i]) {
        int v = e[i];
        if (st[v]) continue;
        if (w[i])                 // 幸运边
            f[v] = sz[1] - sz[v]; // 容斥原理
        else
            f[v] = f[u] + s[u] - s[v]; // 还是容斥原理吧~
        // 最后执行噢
        dfs2(v);
    }
}

// 幸运数字是由 4 和 7 组成的正整数
int check(int n) {
    while (n) {
        if (n % 10 != 4 && n % 10 != 7) return 0;
        n /= 10;
    }
    return 1;
}

int main() {
    memset(h, -1, sizeof h);
    int n;
    cin >> n;
    for (int i = 1; i < n; i++) { // n-1条边
        int a, b, c;
        cin >> a >> b >> c;
        c = check(c); // 如果一条边的权值是一个幸运数字,那么我们就说这条边是一条幸运边
        add(a, b, c), add(b, a, c);
    }
    memset(st, 0, sizeof st);
    dfs1(1);
    memset(st, 0, sizeof st);
    dfs2(1);

    LL ans = 0;
    for (int i = 1; i <= n; i++) ans += s[i] * (s[i] - 1) + f[i] * (f[i] - 1) + s[i] * f[i] * 2;
    printf("%lld\n", ans);
    return 0;
}