题目
Problem Description
ZSX非常嫉妒YZH走上了人生巅峰,于是他也狂刷BZOJ,终于感动了上天,实现了他的三个愿望:出任CEO、迎娶白富美、瘦身30斤,走上了人生巅峰,所以他决定来一次长途旅行好好放松一下。
由于经费问题,ZSX决定仅在CW国完成旅行。CW国一共有n个城市,这n个城市恰好构成一棵树,由于经费实在有限,ZSX决定仅去三个城市,又因为他是一个富有情趣的人,所以他不希望自己去的三个城市在同一条路径上出现。现在ZSX想知道他能去的三个城市有多少种组合。
Input
第一行一个正整数n,表示CW国的城市数量。
接下来n - 1行,每行两个正整数u,v,表示从城市u,v之间有一条树边。
对于100%的数据,n <= 100000, 1 <= u, v <= n
Output
一行一个数,表示答案。
Sample Input
5
1 2
1 3
2 4
2 5Sample Output
2
【样例解释】
{{4,5,3},{4,5,1}}
分析
- 题目大概就是问一棵树里面有多少组点(每组三个点),使得它们(那三个点)不在一条链上。
- 我们可以把问题转换一下,变成求在一条链上的点的组数。(正着求答案也是可以的,只不过分类较多,容斥一下更方便)
- 搜索,枚举路径中间那个点,然后用些技巧可以求出路径数。
程序
#include <cstdio>
#define For(x) for (int h=head[x],o=V[h]; h; o=V[h=to[h]])
#define C3(x) ((x)*(x-1)*(x-2)/6)
int head[100005],to[200005],V[200005],num;
long long n,sz[100005],a[100005],ds,Ans;
void add(){
int u,v; scanf("%d%d",&u,&v);
to[++num]=head[u], head[u]=num, V[num]=v;
to[++num]=head[v], head[v]=num, V[num]=u;
}
long long dfs(int x,int Fa){
sz[x]=1;
For(x) if (o!=Fa){
ds=dfs(o,x);
a[x]+=(sz[x]-1)*sz[o]; //新的点加之前子树内的一个点可以与 x 组成一个路径
sz[x]+=ds;
}
a[x]+=(sz[x]-1)*(n-sz[x]); //子树 x(x除外) 中的点加子树 x 外的点与 x 组成一个路径
Ans-=a[x]; //这些都是不符合的,减掉它们
return sz[x];
}
int main(){
freopen("1.txt","r",stdin);
scanf("%d",&n); for (int i=1; i<n; i++) add();
Ans=C3(n); //先把答案初始化成所有点的 组数
dfs(1,1); //手动把1当成树的根
printf("%lld",Ans);
}
提示
- 数据范围让我们需要开
long long
。 - 时间复杂度估计是
2n
- 你们可以想一想这题拓展:把每组三个点变成
k
个点,复杂度在nk
级别。