洛谷·P3565 [POI2014]HOT-Hotels(树形DP、详解)

来源:互联网 发布:企业cms开源 编辑:程序博客网 时间:2024/06/16 15:10

题目描述

There are towns in Byteotia, connected with only roads.

Each road directly links two towns.

All the roads have the same length and are two way.

It is known that every town can be reached from every other town via a route consisting of one or more (direct-link) roads.
In other words, the road network forms a tree.

Byteasar, the king of Byteotia, wants three luxury hotels erected to attract tourists from all over the world.

The king desires that the hotels be in different towns and at the same distance one from each other.

Help the king out by writing a program that determines the number of possible locations of the hotel triplet in Byteotia.

有一个树形结构,每条边的长度相同,任意两个节点可以相互到达。选3个点。两两距离相等。有多少种方案?

输入输出格式
输入格式:

The first line of the standard input contains a single integer (), the number of towns in Byteotia.

The towns are numbered from to .

The Byteotian road network is then described in lines.

Each line contains two integers and () , separated by a single space, that indicate there is a direct road between the towns and .

In tests worth of the total point number an additional condition ![](h…

输出格式:

The first and only line of the standard output should contain a single integer equal to the number of possible placements of the hotels.

输入输出样例

输入样例#1:
7
1 2
5 7
2 5
2 3
5 6
4 5

输出样例#1:
5

说明

有一个树形结构,每条边的长度相同,任意两个节点可以相互到达。选3个点。两两距离相等。有多少种方案?
这么大一段英文真的很废对不对,直接看最后一句就行了。


树形DP,还是挺有难度的,毕竟是POI的题。

首先确认一下,由于这是一棵树,所以若有三个点两两距离相同,则一定会有一个交集点,它们到这个交集点的距离相同。

那么我们只要枚举这个中心点就行了;

我们以当前枚举的中心点为根节点,将整个无根树旋转,这棵树就变成了有根数。

然后位于同一深度的节点与根节点(中心点)的距离就自然是一样的啦,

但是如果某一深度的两个节点的最经公共祖先不是我们枚举的根节点,就会出岔子。(画一下就看出来了)

就拿样例来说,画完后,当枚举到2为根节点,深度为2时,4、6、7是满足题意的。

所以我们最好的解决方法是在每一棵子树上各选一点凑成三点,这样就能保证他们的最经公共祖先为根节点了。

所以在枚举了根节点后,我们一次只计算其一棵子树。

OK,确认距离相等的思路就是这样的,下面进入数学课时间!

数学数学数学数学数学数学数学数学数学数学数学数学大法好好好好好好好好好。

如何维护这个方案树呢?(我们已经确保了到根节点的距离相等了,不明白的童鞋参照上文在纸上画一下)

先从简单的开始吧(至少得有三棵子树是吧)

假设我们当前枚举的根节点为A,深度为i; //其实这些对于说明没卵用

那么三棵子树在这一深度上的节点数分别为a、b、c;

那么新增加的答案自然就是a*b*c;(小学的乘法原理)

如果这时候,第四个子树上的节点数为d,我们来看一下答案是怎么变化的。

有一下三种情况:d*a*b、d*a*c、d*b*c。我们合并一下:new_ans=d*(a+b+c);

再来一棵子树,上面的节点数为e,那么变化为:

有6种情况:e*a*b、e*a*c、e*b*c、e*a*d、e*b*d、e*c*d;

合并一下:new_ans=e*(a*b+a*c+b*c+d*a+d*b+d*c);

看出了什么规律了吗?

没看出,好吧;

如果用一个sigma_2维护a*b+b*c+a*c………等这一式子;每次新得到的节点数为K

那么新增加的答案数为sigma_2*K;

对于sigma_2,我们继续观察一下:

三个数到四个数:a*b+b*c+a*c–>a*b+b*c+a*c+a*d+b*d+c*d即新增加d*(a+b+c)

四个数到五个数:太长了………..自己写一下吧,新增加的为e*(a+b+c+d)

这下看出来了吧,如果记sigma_1为之前得到的节点总数(之前子树此层节点之和)

那么有以下规律:若新得到的节点数为K,那么sigma_2=K*sigma_1;然后sigma_1=sigma_1+K;

所以对于递推方案数的分析,我们就分析完了。但估计各位还是不太清楚具体实现,那我来总结一下:

设tot[i]为当前子树第i层的节点数,我们用c1[i]维护第i层(距离)的sigma_1 ,用c2[i]维护第i层(距离)的sigma_2;

那么有一下框架:

1.枚举根节点k
2.枚举每一棵子树,计算第i层的tot[i];
3.重头戏,计算部分:
- ans=ans+tot[i]*c2[i];
- c2[i]=c2[i]+c1[i]*tot[i]
- c1[i]=c1[i]+tot[i]
- tot[i]=0//记得清零!!!!!

特别提醒一下,每枚举一个新的根节点,要记得把c1、c2清零。 本题的答题思路就是这样了,希望能够帮到大家。

其实上面的板子(总结)就是本题核心部分的伪代码了,但还是把完整的代码发一下吧。

具体实现代码:

//设子树中第k层的节点数分别为a1、a2、a3、。。。。。。、an// 那么3个时开始统计答案,有a1*a2*a3一种//  当4个子树时://  有a4*a1*a2 a4*a2*a3 a4*a1*a3 三种(新增) // 合计:a4*(a1*a2+a2*a3+a3*a1);//  用c2 维护a1*a2+a2*a3.....这一式子//  关注到c2的变化:c2[前]a1*a2*a3-->c2[前]+(a1+a2+a3)*a4// 以此类推 ;  令tot为此子树的节点个数 、 c1为这一层之前所有节点和(a1+a2+...+at) // 则可以得到递推式://   ans+=c2[i]*tot[i];//   c2[i]+=tot[i]*c1[t];//   c1[t]+=tot[i];         //算完后记得把tot[i]清零 //注意枚举根节点,最后求一个总和      #include<bits/stdc++.h>#define IL inline#define uev(i,j) for(i=head[j];i;i=a[i].next)#define ll long long#define RG register#define IL inlineusing namespace std;struct pic{    ll next,to;}a[10001];ll head[10001];ll c1[5001],c2[5001];ll tot[5001],cnt,dep;ll ans=0;IL ll read(){    ll date=0,m=1;char ch=0;    while((ch<'0' || ch>'9')&& ch!='-')ch=getchar();    if(ch=='-'){        m=-1;        ch=getchar();    }    while(ch>='0' && ch<='9'){        date=date*10+ch-'0';        ch=getchar();    }return date*m;}IL void connect(ll x1,ll x2){    cnt++;    a[cnt].to=x2;    a[cnt].next=head[x1];    head[x1]=cnt;}void getdeep(ll now,ll father,ll deep)  //deep---距离根节点的深度 {    RG int i;    dep=max(dep,deep);tot[deep]++;    uev(i,now)    {        int nex=a[i].to;        if(nex==father)continue;        getdeep(nex,now,deep+1);    }    return;}int main(){    freopen("A.in","r",stdin);    RG ll N,i,x1,x2,j,t;    N=read();cnt=0;    for(i=1;i<=N-1;i++){        x1=read();x2=read();        connect(x1,x2);        connect(x2,x1);    }    ans=0;    for(i=1;i<=N;i++)//------------------枚举根节点    {        memset(c1,0,sizeof(c1));        memset(c2,0,sizeof(c2));        uev(j,i)//--------------------枚举每一棵子树        {           ll nex=a[j].to;           dep=0;           getdeep(nex,i,1);           for(t=1;t<=dep;t++)           {               ans+=tot[t]*c2[t];               c2[t]=c2[t]+c1[t]*tot[t];               c1[t]=c1[t]+tot[t];               tot[t]=0;           }         }     }    printf("%lld",ans);    return 0;}

个人认为这应该是树形DP中较难的题了,A了这个的童鞋些其他的树形DP水题应该就很轻松了,哈哈哈哈。

PS.别直接垮我的代码哦,转载请申明出处!