题目链接
https://ac.nowcoder.com/acm/contest/7509/D
解题思路
实现过程
对于添加边的操作而言,
如果两点之间有边,那么可以忽略本次加边操作;
如果没边:
——如果两点的度均为0,添加一条边之后,大小不为一的树的数量++,即ans++;
——如果两点的度均不为0,说明两点都分别与两棵树相连,添加一条边之后,两树合并为一树,ans--;
(因为可能存在重复的边,因此用set存边而不用vector)判断完毕之后,通过set的insert函数实现插入新边。
对于删除边的操作而言,
如果两点之间没边,那么可以忽略本次删边操作;
如果有边:
——如果两点的度均大于等于2,说明两点除了相互连接外,还分别与其他的两棵树相连,删除两点之间的边之后,一树变两树,ans++;
——如果两点的度均等于1,说明两点仅仅相互连接,删除两点之间的边之后,一棵树变成了两个点,ans--;
判断完毕之后,通过set的erase函数将被删除的边删去。
为什么其他情况对ans没影响?
对于添边操作而言,找一下会让ans++的情况有哪些:
只有当添加的边连接了两个单点才会ans++吧,只要两点中有一个不是单点,添边只能算是把一个点连接到了一棵已经存在的树上,这不会影响ans;两个点都不是单点那就更不可能了。
对于添边操作而言,找一下会让ans--的情况有哪些:
要让ans--,必须把两棵树连成一棵树才会使ans--,两点的度数>=1说明了两点不是单点,是分别属于两棵树的点,连接两树。
对于删边操作而言,找一下会让ans++的情况有哪些:
要让ans++,必须把一棵树拆成两棵树,拆成的两棵树不能是单点,就得让度都>=2。
对于删边操作而言,找一下会让ans--的情况有哪些:
让ans++时,必须让拆得的两树为非单点,而要让ans--,就必须让拆得的树为单点,只有这样才能实现从有一棵非单点树,变成单点,实现ans--。
综上,所有能让ans++,让ans--的操作都包含在上述过程中了,其他情况不影响ans。
AC代码
#include<bits/stdc++.h> #define ll long long using namespace std; const int N=2e5+10; ll c1[N],c2[N],c[N]; int op[N]; int n,cnt; set<ll> t[N]; int main(){ cin>>n; for(int i=1;i<=n;i++){ cin>>op[i]; if(op[i]==3) continue; else{ cin>>c1[i]>>c2[i]; c[++cnt]=c1[i]; c[++cnt]=c2[i]; } } sort(c+1,c+cnt+1); int num=unique(c+1,c+cnt+1)-c-1;//unique返回不重复序列的下一个元素位置 ll ans=0; for(int i=1;i<=n;i++){ c1[i]=lower_bound(c+1,c+num+1,c1[i])-c; c2[i]=lower_bound(c+1,c+num+1,c2[i])-c; ll u=c1[i],v=c2[i]; if(op[i]==1){ if(t[u].find(v)!=t[u].end()) continue;//u,v之间存在边,无法添加,continue if(!t[u].size() && !t[v].size()) ans++; if(t[u].size() && t[v].size()) ans--; t[u].insert(v); t[v].insert(u); } else if(op[i]==2){ if(t[u].find(v)==t[u].end()) continue;//u,v之间若不存在边,无法删除,continue if(t[u].size()>=2 && t[v].size()>=2) ans++; if(t[u].size()==1 && t[v].size()==1) ans--; t[u].erase(v); t[v].erase(u); } else{ cout<<ans<<endl; } } }
总结
并不是很难实现的题,但是确实挺考思维的。
我不会,我好废!
难题不愿看,简单题不会,难道这就是传说中的菜鸡吗?答案是肯定的。