P8882 成熟时追随原神
upd on 2024/01/29:修改一处笔误。
很好的一道题,使我草元素充盈。
看一眼样例:似乎没有需要乘逆元的结果。
加上比赛声明:本次比赛的题目较为特别,不属传统 OI 题目的范畴。
再回顾上一道 P8881,基本确定又是诈骗题。
定义「树形框架」等价于初始给出的那棵树。
先重新把题面理解一下:给定一个树形框架,进行若干次操作。对于每次操作,你需要查询当前框架下,进行连边操作后的连通块个数。
而根据题目描述,连边操作即为:对于每个非叶子节点 $u$,随机选取一个子结点 $v$,并在 $u$ 和 $v$ 之间连边。
显然,连边操作次数等于非叶子结点数目,并且所连边不存在自环或重边。由于连边操作次数明显小于当前结点总数,因此连通块个数即为节点总数与连边条数之差,即叶子结点数目。
于是我们发现,连通块个数的期望与连边方式没有任何关系,它等于叶子结点个数。对于每次操作,只需要维护当前树形框架的叶子结点数量即可。
设当前状态下叶子结点数量为 $klee$(注意叶子结点是度数为 $1$ 的充分不必要条件,两者并不等价):
- Add:如果 $u$ 不是叶子结点,$klee\gets klee+1$;如果 $u$ 是叶子结点,$klee$ 不变。
- Del:如果 $u$ 的父结点在删掉 $u$ 后变为叶子结点,$klee\gets klee-1$;否则 $klee$ 不变。
- Upd:如果原来的根度数为 $1$,那么操作后就会变成叶子结点,$klee\gets klee+1$;如果新的根原来是叶子结点,$klee\gets klee-1$。
用 set 代替 vector 进行存图,可以在 $n\log n$ 级别的复杂度内解决。
#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int MAXN=2e5+10,P=998244353;
int n,root=1,klee,m;
set<int>T[MAXN*2];
bool check(int u)
{
if((u==root&&T[u].empty())||(u!=root&&T[u].size()==1))return true;
else return false;
}
int main()
{
ios::sync_with_stdio(false);
cin>>n;
for(int i=2;i<=n;i++)
{
int fa;
cin>>fa;
T[i].insert(fa);
T[fa].insert(i);
}
for(int i=2;i<=n;i++)
{
if(T[i].size()==1)klee++;
}
cout<<klee<<'\n';
cin>>m;
for(int i=1;i<=m;i++)
{
string op;
int u;
cin>>op>>u;
if(op=="Add")
{
if(!check(u))klee++;
T[u].insert(n+i);
T[n+i].insert(u);
}
if(op=="Del")
{
T[*T[u].begin()].erase(u);
if(!check(*T[u].begin()))klee--;
}
if(op=="Upd")
{
if(T[u].size()==1&&T[root].size()!=1)klee--;
if(T[u].size()!=1&&T[root].size()==1)klee++;
root=u;
}
cout<<klee<<'\n';
}
return 0;
}