【树形dp练习】Y

时间:2023-02-13 17:26:55

题面

对于给定的树T,计算集合{A,B,C}的数量,其中:
1、A,B,C是树T的节点
2、不存在经过A,B,C的简单路径

分析

我猜题目叫Y的原因是因为要求的三个点构成一个Y的形状qvq

【树形dp练习】Y

先看这个粉色的部分,考虑怎么计算第二部分的答案呢?

根据组合数学的思想,可以从3,4,8为根的子树内选两个(一个子树内只能选一个),然后再从除了以2为根的子树的其他所有结点中选一个,相乘。

第一部分的答案就更简单了,就是这个子树的所有小子树的大小的乘积,因为会被三次计算,所以最后除以三

然而!!

上面的方法过于复杂,我们可以根据正难则反的思想。计算三个点在一个简单路径上的数量,再用总路径数C(N,3)减去它。

三个点在一个简单路径:对于有儿子的点,选择他,并在以他为根的子树中选1个和除了他的子树以外的所有点中选1个

对于无儿子的点,除了他的所有点中选择2个。

最后因为每条路径被三个点计算了,总简单路径数除以3

代码

法一的代码。法二的代码更为简洁,可以自行实现

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define ll long long 
#define N 200010
ll t,n,p1,p2,cnt;
ll first[N],siz[N];
struct email
{
    ll u,v;
    ll nxt;
}e[N*4];
inline void add(ll u,ll v)
{
    e[++cnt].nxt=first[u];first[u]=cnt;
    e[cnt].u=u;e[cnt].v=v;
}

inline void init()
{
    cnt=p1=p2=0;
    memset(siz,0,sizeof(siz));
    memset(first,0,sizeof(first));    
}

void dfs(ll u,ll fa)
{
    ll mulv=0;siz[u]=1;
    for(ll i=first[u];i;i=e[i].nxt)
    {
        ll v=e[i].v;
        if(v==fa)continue;
        dfs(v,u);
        mulv+=(siz[u]-1)*(siz[v]);
        siz[u]+=siz[v];
    }
    p1+=mulv*(n-siz[u]);
    for(ll i=first[u];i;i=e[i].nxt)
    {
        ll v=e[i].v;
        if(v==fa)continue;
        p2+=siz[v]*(mulv-siz[v]*(siz[u]-1-siz[v]));
    }
}
int main()
{
    while(scanf("%lld",&n)==1)
    {
        init();
        for(ll i=1;i<n;i++)
        {
            ll u,v;
            scanf("%lld%lld",&u,&v);
            add(u,v);add(v,u);
        }
        dfs(1,0);
        printf("%lld\n",p1+p2/3);
    }
    return 0;
}