AtCoder Regular Contest 148

C Lights Out on Tree

挺好的一道题,简单写一下题解吧。
首先有挺多很 naive 的结论:

  • 每个节点按两遍等于没按。
  • 熄灭所有的灯只有一种方案。

其实将灯熄灭的方案无非就是从上往下 dfs,如果当前灯是量的,就在当前节点按一下,否则不按。这样我们就可以写出暴力了。

考虑一下,如果树上只有一个节点 \(u\) 开始亮灯,那么需要按的灯即为 \(u\)\(u\) 的所有儿子,设这个集合为 \(\{v\}\)

进一步考虑一下,若初始的亮灯集合为 \(\{u_1,u_2,u_m\}\),那么所有需要按的集合即为 \(\{v_1,v_2,v_m\}\),再根据上面的第一个结论:每个节点按两遍等于没按,所以只需要求出在 \(\{v_1,v_2,v_m\}\) 集合中出现次数为奇数的节点个数。

可以用线段树维护,但是进一步观察发现:每个节点如果出现两遍当且仅当它的父亲也在集合 \(\{u_1,u_2,u_m\}\) 中,这样我们就可以在 \(\mathcal O(n)\) 的时间内解决本题了。

点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int N=2e5+5;
int n,q,son[N],p[N],m,v[N],mp[N];
signed main()
{
	cin>>n>>q;
	for(int i=1;i<=n;++i)son[i]++;
	for(int i=2;i<=n;++i){cin>>p[i];son[p[i]]++;}
	while(q--)
	{
		int ans=0;cin>>m;
		for(int i=1;i<=m;++i)
		{
			cin>>v[i];mp[v[i]]=1;ans+=son[v[i]];
			if(mp[p[v[i]]])ans-=2;//如果当前节点的父亲在集合中,答案减2 
		} 
		for(int i=1;i<=m;++i)mp[v[i]]=0;
		cout<<ans<<endl;
	}
	return 0;
}
posted @ 2022-09-18 12:29  lnwhl  阅读(83)  评论(0编辑  收藏  举报