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;
}