HDU 4705 Y (树形DP)

题意

给定一棵树,计算数集{A,B,C}的个数,其中A,B,C是树上的节点,且不存在一条路径覆盖A,B,C。

思路

朴素的想法是枚举“Y”的中点,然后枚举三条树枝i,j,k,答案就是sigma(Si*Sj*Sk) = [( sigma(Si) )^3 - 3*sigma(Si)*sigma(Si^2) + 2*sigma(Si^3)] / 6,其中Si表示i子树上节点个数。但是这样太麻烦了。 我们考虑用补集来做。计算A,B,C被一条路径覆盖的个数。那么这样假设A<B<C,我们就可以枚举B,然后枚举两个树枝i,j,答案就是sigma(Si*Sj) =[ ( sigma(Si) )^2 - sigma ( Si^2 ) ] / 2,时间复杂度O(n)。

代码

  [cpp] #include <iostream> #include <cstdio> #include <cmath> #include <algorithm> #include <string> #include <cstring> #include <vector> #include <set> #include <stack> #include <queue> #define MID(x,y) ((x+y)/2) #define MEM(a,b) memset(a,b,sizeof(a)) #define REP(i, begin, end) for (int i = begin; i <= end; i ++) #pragma comment(linker, "/STACK:1024000000,1024000000") using namespace std; typedef long long LL; typedef vector <int> VI; const int MAXV = 100005; int n; VI adj[MAXV]; LL res, num[MAXV]; bool vis[MAXV]; void dfs(int u){ vis[u] = true; int son = 0; num[u] = 1; VI tmpadj; for (int i = 0; i < (int)adj[u].size(); ++ i){ int v = adj[u][i]; if (!vis[v]){ tmpadj.push_back(v); son ++; dfs(v); num[u] += num[v]; } } if (son > 0){ if (u == 1 && son == 1) return ; LL uans = (LL)(n-1)*(n-1); for (int i = 0; i < (int)tmpadj.size(); ++ i){ int v = tmpadj[i]; uans -= (LL)num[v] * num[v]; } uans -= (LL)(n-num[u])*(n-num[u]); uans /= 2; res += uans; } return ; } int main(){ while(scanf("%d", &n) != EOF){ REP(i, 0, n) adj[i].clear(); REP(i, 1, n-1){ int u, v; scanf("%d %d", &u, &v); adj[u].push_back(v); adj[v].push_back(u); } res = 0; MEM(num, 0); MEM(vis, false); dfs(1); printf("%I64d ", (LL)n*(n-1)*(n-2)/6 - res); } return 0; } [/cpp]