1 条题解
-
0
自动搬运
来自洛谷,原作者为

gan1234
† † † †搬运于
2025-08-24 22:43:24,当前版本为作者最后更新于2022-12-15 18:18:35,作者可能在搬运后再次修改,您可在原文处查看最新版自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多
以下是正文
分析
首先我们要知道,一个图里连通块的数量是点数减去边数。
分析题目,每个非叶结点都从子结点中选取一个点 与之连接,也就是说有多少非叶结点就有多少条边。 我们令 为叶结点数量,那么 就是边的数量,连通块的数量就是 ,也就是 。
我们发现叶结点有多少,连通块就有多少,题目就简化为求叶结点的数量。
做法
如何求叶结点数量?首先用一个变量,储存初始树的叶结点数量,再分别对于每个操作更新答案。
- Add 操作:如果 是非叶结点,叶结点数量加一,如果 是叶结点,叶结点总数不变。
- Del 操作:如果 的父结点是非叶结点,叶结点数量减一,如果 的父亲是叶结点,叶结点总数不变。
- Upd 操作:如果原来的根只有一个儿子,那么操作后就会变成叶结点,叶结点数就加一。如果新的根原来是叶结点,操作后叶结点数就减一。
因为要进行删边和访问父结点操作,所以可以用 set 来维护每个点的邻点。如果一个点只有一个邻点,那么这个点就是叶结点。叶结点的父结点就是唯一的邻点。
代码
最多 个点和删边 次,所以复杂度为
#include<bits/stdc++.h> using namespace std; unordered_set<int>s[400005]; int n,m,rt=1; int ans; int f(int x){//判断是否是叶结点 return (x==rt&&s[x].size()==0)||(x!=rt&&s[x].size()==1); } int main(){ cin>>n; int x; for(int i=2;n>=i;i++){ cin>>x; s[i].insert(x);s[x].insert(i); } for(int i=2;n>=i;i++) if(s[i].size()==1)ans++; cout<<ans<<endl; cin>>m; string str; for(int i=1;m>=i;i++){ cin>>str>>x; if(str=="Add"){ if(!f(x))ans++; s[x].insert(n+i);s[n+i].insert(x); } if(str=="Del"){ s[*s[x].begin()].erase(x); if(!f(*s[x].begin()))ans--; } if(str=="Upd"){ int y=rt;rt=0; if(f(x)&&!f(y))ans--; if(!f(x)&&f(y))ans++; rt=x; } cout<<ans<<endl; } return 0; }
- 1
信息
- ID
- 8161
- 时间
- 4000ms
- 内存
- 128MiB
- 难度
- 4
- 标签
- 递交数
- 0
- 已通过
- 0
- 上传者