题目链接

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

总结

并不是很难实现的题,但是确实挺考思维的。
我不会,我好废!
难题不愿看,简单题不会,难道这就是传说中的菜鸡吗?答案是肯定的。