51nod3105 小明喜欢树 V2

3105 小明喜欢树 V2

小明非常喜欢树,有一天小明得到这样一棵树,每个树节点都有一个编号,有n个节点的树的编号为1到n,每个编号代表该节点的海拔高度,现在小明要在这颗树上找到一些路径,从起点到终点需要满足海拔先单调上升后单调下降的性质,起点或终点不同即为不同的路径,问满足条件的路径有多少条,聪明的你可以帮助小明解决这个问题吗?

输入

第一行输入一个整数n(1 <= n <= 300000)
接下来n-1行,每行有两个整数,表示这两个节点连在一起

输出

输出路径的个数。

数据范围

对于10%的数据,1<= n <= 8
对于50%的数据,1 <= n <= 1024
对于100%的数据,1 <= n <= 300000

输入样例

5
1 5
4 5
2 4
3 4

输出样例

8

样例解释

符合条件的路径有

1-5-4

1-5-4-3

1-5-4-2

3-4-2

另外四条路径反过来即可,一共八条路径。

解析:

假如我们知道,以节点 ii 为起点的单调递减的路径数量Dp[i],那么从这些路径中,任选2条不在同一棵子树下的,便可以快速计算出,有多少条先升后降的路径以该节点为最高点。

以上面的示例为例:节点 5 包括 4 条单调递减的路径,因此以 5 为最高点的路径数量为 1×3+3×1=6。

有了节点对应的 Dp[i]Dp[i],便可以在O(n)时间内计算出最终的结果。

那么如何快速计算Dp[i]呢?考虑使用树形Dp。

我们可以利用一次 DFSDFS 求出所有的Dp[i],这个DFS有2个阶段,第一阶段,只处理编号比自己小的子节点,状态转移是从子节点到父节点。

Dp[parent]=\sum Dp[child]

经过第一阶段处理之后,统计出了所有来自子节点的递减路径。这部分的处理需要先递归再累加。

在DFS的第二阶段,只处理编号比自己大的子节点,并将当前节点的计数,累加到子节点,状态转移从父节点到子节点。

经过第二阶段处理之后,统计出了所有来自父节点的递减路径。这部分的处理需要先累加再递归,与第一阶段正好相反。

有了这两部分的累加计数,我们再调用最初阶段所说的统计方法,就可以快速计算出所有符合条件的路径数量。

以样例为例,求得的Dp数组如下:

Dp[5]=4,Dp[4]=2,Dp[1]=Dp[2]=Dp[3]=0。

放代码:

#include<bits/stdc++.h>
using namespace std;
const int N=1<<10;
inline char nc() 
{
    static char buf[N],*p1=buf,*p2=buf;
    return p1==p2&&(p2=(p1=buf)+fread(buf,1,N,stdin),p1==p2)?EOF:*p1++;
}
template<typename T>
inline int scan(T &x) 
{
    char c=nc();
    if(c==EOF)return EOF;
    for(x=0;isdigit(c);c=nc())x=x*10+(c^48);
    return x;
}
template<typename T, typename...Args>
inline int scan(T &x, Args&...args) 
{
    return scan(x),scan(args...);
}
struct Edge
{
    int to,next;
    Edge() {}
    Edge(int a,int b):to(a),next(b) {}
}E[600005];
int tot=0,head[300005];
long long ans=0,DP[300005]={0};
void add(int x,int y)
{
    E[++tot]=Edge(y,head[x]);
    head[x]=tot;
    E[++tot]=Edge(x,head[y]);
    head[y]=tot;
}
void DFS1(int x,int fa)
{
	int i,j;
	for(i=head[x];i;i=E[i].next)
	{
		j=E[i].to;
		if(j==fa)continue;
		DFS1(j,x);
		if(j<x)DP[x]+=DP[j]+1;
	}
}
void DFS2(int x,int fa)
{
	int i,j;
	long long s=0;
	for(i=head[x];i;i=E[i].next)
	{
		j=E[i].to;
		if(j<x)ans+=s*(DP[j]+1),s+=DP[j]+1;
		if(j==fa)continue;
		if(j>x)DP[j]+=DP[x]+1;
		DFS2(j,x);
	}
}
int main()
{
	int i,j,n;
	scan(n),n--;
	while(n--)scan(i),scan(j),add(i,j);
	DFS1(1,0),DFS2(1,0);
	printf("%lld\n",ans<<1);
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值