这个题其实还是比较简单的,有一点点图论基础都能做。题目的意思n次操作,操作1给a,b加边(重边直接忽略)。操作2给a,b删边(不存在则忽略)。操作3 输出当前不为1的树的数量。
题目数据非常amazing啊,直接把点拉到了1e8,出题人甚至专门写了一句话要玩家注意范围。对此,我们离散一下就好了。轻松破解
然后就是如果进行三种操作:我们定义一个全局变量 ans,初始为0。
操作1:此种操作一共会有三种情况:
1. 一个点与另外一个点连接,此时满足条件的树会增加1个,于是给 ans加1。然后把两个点连起来
2. 一个点与另外一棵树连接,这种情况对我们的ans没有影响,直接把两个点连起来
3. 一棵树和另外一棵树,这种情况把两棵树连接成了一棵,总的树的数量就会减少1棵,给ans减1即可,直接把两棵树连接起来
操作2:与操作1类似,建议自己推一下。结论和操作1正好相反。这两个操作记得要判断重边和不存在边
操作3:经过上面的操作,操作3就变得很简单了,直接输出ans就好了
因为要反复进行查询,删边和加边操作,用set二维组存边,link[u]中间存放了所有和u相连的点。满足如下规则:
1.link[u].size()==0 说明u没有和任何点相连,是一个单独存在的点
2.link[u].size()>0 说明u和其他点连接,是一棵树的一部分
其他的看代码吧,有注释。对于离散操作。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define js ios::sync_with_stdio(false);cin.tie(0); cout.tie(0)
const ll MOD = 1e9 + 7, inf = 0x3f3f3f3f;
inline ll read() {
    ll s = 0, w = 1;
    char ch = getchar();
    for (; !isdigit(ch); ch = getchar()) if (ch == '-') w = -1;
    for (; isdigit(ch); ch = getchar())    s = (s << 1) + (s << 3) + (ch ^ 48);
    return s * w;
}
enum {maxn=100010};
set<int> link[maxn],lisan;
int li[maxn],tot;
int ans = 0;
int op[maxn], ta[maxn], tb[maxn];
int main() {
    ll n = read();
    for (int i = 0; i < n; ++i) {
        op[i] = read();
        if (op[i] != 3) {
            ta[i] = read(), tb[i] = read();
            lisan.insert(ta[i]); lisan.insert(tb[i]);
        }
    }
    for (auto p : lisan) li[tot++] = p;
    for (int i = 0; i < n; ++i) {
        if (op[i] == 3) continue;
        ta[i] = lower_bound(li,li+tot,ta[i])-li+1;
        tb[i] = lower_bound(li, li + tot, tb[i]) - li+1;
    }
    //上面是离散过程,比赛太仓促,用set离散的,可以自己换一下用sort+unique离散。
    for (int i = 0; i < n; ++i) {
        int u, v;
        switch (op[i])
        {
        case 1:
            u = ta[i], v = tb[i];
            if (link[u].find(v)!=link[u].end()) break;
            //如果边存在,跳过此次操作
            if (link[u].size() == 0 && link[v].size() == 0) ans++;
            if (link[u].size() && link[v].size())ans--;
            //判断此次操作类型
            link[u].insert(v);
            link[v].insert(u);
            //连接两个点
            break;
        case 2:
            u = ta[i], v = tb[i];
            if (link[u].find(v) == link[u].end())break;
            //如果边不存在,跳过此次操作
            if (link[u].size() > 1 && link[v].size() > 1) ans++;
            if (link[u].size() == 1 && link[v].size() == 1) ans--;
            //判断操作类型
            link[u].erase(v);
            link[v].erase(u);
            //删除一条边
            break;
        case 3: cout << ans << endl;
            //直接输出
            break;
        }
    }
}