51nod1588 幸运树 树形dp统计树上方案数

发布于 2021-03-27  59 次阅读



传送门:https://www.51nod.com/Challenge/Problem.html#problemId=1588

题意

定义幸运数字只由4和7组成,比如4,7,47。

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

问,存在多少组这样的三元组。

思路

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

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

设f[u]为以u为根节点的子树中,有几个点到u的路径中存在幸运数字。
设h[u]为以u为根节点的子树外,有几个点到u的路径中存在幸运数字。

这样,我们的j和k的选择就可以在f中选择,或者h中选择,或者f和h中选择。

即i的贡献为$f[i]*(f[i]-1)+h[i]*(h[i]-1)+f[i]*h[i]*2$

然后就是处理f和h。

dfs过程中:

  • 如果u和v的边是幸运数字,则$f[u]+=siz[v]$
  • 否则$f[u]+=f[v]$
  • 如果v和u的边是幸运数字,则$h[v]+=siz[1]-siz[v]$
  • 否则$h[v]+=h[u]+f[u]-f[v]$

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

Code

#include "bits/stdc++.h"
using namespace std;

typedef long long ll;
const int N = 1e6 + 10;

struct Edge {
    int v, w;
};
vector<Edge> g[N];

ll f[N], h[N];
int siz[N];

void dfs1(int u, int fa) {
    siz[u] = 1;
    for(auto e : g[u]) {
        int v = e.v;
        if(v == fa) continue;
        dfs1(v, u);
        siz[u] += siz[v];
        if(e.w) f[u] += siz[v];
        else f[u] += f[v];
    }
}

void dfs2(int u, int fa) {
    for(auto e : g[u]) {
        int v = e.v;
        if(v == fa) continue;
        if(e.w) h[v] = siz[1] - siz[v];
        else h[v] = h[u] + f[u] - f[v];
        dfs2(v, u);
    }
}

int check(int n) {
    while(n) {
        if(n % 10 != 4 && n % 10 != 7) return 0;
        n /= 10;
    }
    return 1;
}

void solve() {
    int n; cin >> n;
    for(int i = 1;i < n; i++) {
        int u, v, w; cin >> u >> v >> w;
        w = check(w);
        g[u].push_back(Edge{v, w});
        g[v].push_back(Edge{u, w});
    }
    dfs1(1, -1);
    dfs2(1, -1);
    ll ans = 0;
    for(int i = 1;i <= n; i++) ans += f[i] * (f[i] - 1) + h[i] * (h[i] - 1) + f[i] * h[i] * 2;
    cout << ans << endl;
}

signed main() {
    solve();
}

本当の声を響かせてよ