gdfzoj#236 | 提高组练习题16 Set

题目大意 \(Description\)

给定一棵树,求合法的集合 \(\{ A,B,C \}\) 的个数。一个集合是合法的当且仅当不存在任意一条路径同时覆盖 \(A,B,C\) 三个点。

题解 \(Solution\)

如果直接按题目大意做很难处理其约束条件,不妨反面思考。问题转化为:求树上有多少组\(\{ A,B,C\}\)三个节点可以被同一条路径覆盖。

考虑固定其中一个点,那么它同以它某个子节点 \(v\) 为根的子树内的某个点(以下称为第\(1\)类点),和除\(v\) 为根的子树外的某个点(以下称为第\(2\)类点)构成一条路径。枚举这个点,同时记录一个 \(sz_i\) 数组表示以它为根的子树大小。那么对于树上的某个节点 \(u\) ,第一类点有 \(sz_v\) 个,第二类点有 \(n-sz_v-1\) 个,由乘法原理,节点 \(u\) 对答案的贡献即为 $ \sum\limits_{v \in son_u} sz_v (n-sz_v-1)$ 。

但是照着上面的式子直接上是会 \(WA\) 的,原因如下图(纯手绘,图丑勿喷):

plain.png

可以看出,\(u\) 节点到 \(v\) 节点的路径在枚举他们的 \(LCA\) 的时候被重复计算了,所以为了防止被重复计算,在我们枚举一个点 \(u\) 时,同时维护一个 nowsum ,初始化为 nowsum=n,每次计算完以 \(u\) 的子节点 \(v\) 为根的子树后减去 \(sz_v\) ,最终答案即为 \({n \choose 3} - \sum\limits_{u=1}^{n}\sum\limits_{v \in son_u} sz_v ( nowsum - sz_v - 1)\),参考代码如下。

\(std\) 的代码(仅供参考)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N=1e5+50;
ll n,sz[N],head[N],cnt,sum,ans;
inline ll read()
{
	ll sum=0,f=1;
	char ch=0;
	while(!isdigit(ch))
	{
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(isdigit(ch))
	{
		sum=sum*10+(ch^48);
		ch=getchar();
	}
	return sum*f;
} 
inline void write(ll x)
{
	if(x<0)
	{
		x=-x;
		putchar('-');
	}
	if(x>9) write(x/10);
	putchar(x%10+'0');
}
struct edge
{
	int to,nxt;
}e[N<<1];
inline void add(int u,int v)
{
	e[++cnt].to=v;
	e[cnt].nxt=head[u];
	head[u]=cnt;
}
void dfs(int u,int fa)
{
	sz[u]=1;
	int nowsum=n;
	for(int i=head[u];i;i=e[i].nxt)
	{
		int v=e[i].to;
		if(v==fa) continue;
		dfs(v,u);
		sum+=sz[v]*(nowsum-sz[v]-1);//乘法原理统计答案 
		nowsum-=sz[v];
		sz[u]+=sz[v];//同时更新sz[u] 
	}
}
int main(void)
{
	n=read();
	for(int i=1;i<n;i++) 
	{
		int u=read(),v=read();
		add(u,v),add(v,u);
	}
	dfs(1,-1);
	ans=n*(n-1)*(n-2)/6;//即C(n,3),注意不能预处理阶乘,因为题目没有取模 
	write(ans-sum);
	putchar('\n');
	return 0;
}
posted @ 2020-04-29 18:31  L_G_J  阅读(105)  评论(0编辑  收藏  举报