轮舞前夕

题意

给你一棵树,求最小点覆盖集的元素个数和方案个数。

分析

f[i][0],f[i][1],f[i][2] 分别为
0:以 i 节点为根节点的子树除了 i 节点都被覆盖的最小值;
1:以 i 节点为根节点的子树都被覆盖,且 i 节点不在集合中的最小值。
2:以 i 节点为根节点的子树都被覆盖,且 i 节点在集合中的最小值。
转移显然,方案数随 f 转移即可。

#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;

typedef long long LL;
const int N = 1e5 + 10;
const LL P = 1e9 + 7;
int n,tot;
int to[N * 2],next[N * 2],g[N];
int f[N][3],F[3];
LL cnt[N][3],nt[3];

void add(int x,int y) {
    to[++ tot] = y;
    next[tot] = g[x];
    g[x] = tot;
}

int calc(int x,int y) {
    if (x == 2) return 2;
    if (y == 2 || x == 1 && y == 1) return 1;
    return 0;
}

void dfs(int u,int fa) {
    for (int i = 0;i <= 2;i ++) cnt[u][i] = 1;
    f[u][2] = 1,f[u][1] = P,f[u][0] = 0;
    for (int i = g[u];i;i = next[i]) {
        int v = to[i];
        if (v == fa) continue;
        dfs(v,u);
        for (int j = 0;j <= 2;j ++) F[j] = P,nt[j] = 0;
        for (int j = 0;j <= 2;j ++) 
            for (int k = 0;k <= 2;k ++) {
                if (j != 2 && k == 0) continue;
                int l = calc(j,k);
                F[l] = min(F[l],f[u][j] + f[v][k]);
            }
        for (int j = 0;j <= 2;j ++)
            for (int k = 0;k <= 2;k ++) {
                if (j != 2 && k == 0)continue;
                int l = calc(j,k);
                if (f[u][j] + f[v][k] == F[l]) 
                    nt[l] = (nt[l] + cnt[u][j] * cnt[v][k]) % P;
            }
        memcpy(f[u],F,sizeof(f[u]));
        memcpy(cnt[u],nt,sizeof(nt));
    }
}

int main() {
    scanf("%d",&n);
    for (int i = 1;i < n;i ++) {
        int x,y;
        scanf("%d%d",&x,&y);
        add(x,y);
        add(y,x);
    }
    dfs(1,0);
    int ans = min(f[1][1],f[1][2]);
    int ansn = 0;
    for (int i = 1;i <= 2;i ++) if (f[1][i] == ans) ansn = (ansn + cnt[1][i]) % P;
    printf("%d\n%d",ans,ansn);
}

你可能感兴趣的:(轮舞前夕)