1. 程式人生 > 實用技巧 >線段樹分治+可撤銷並查集 [2020牛客暑期多校訓練營(第八場)All-Star Game

線段樹分治+可撤銷並查集 [2020牛客暑期多校訓練營(第八場)All-Star Game

線段樹分治+可撤銷並查集 2020牛客暑期多校訓練營(第八場)All-Star Game

題目大意:

題解:

線段樹分治+可撤銷並查集。

首先講講這兩個演算法:

線段樹分治呢,其實和線段樹差不多,不過有一個分治的思想在裡面,可以多刷幾個這種型別的題目。

關鍵是把一個修改看成一個區間,每個詢問是一個葉子,修改線上段樹上打標記

可撤銷並查集:

這個就很簡單了,並查集不用路徑壓縮而是按秩合併,這樣可以容易刪除,每次放進去的同時記錄到一個棧裡面,之後再刪除,不太理解的可以看看程式碼。

知道以上兩個演算法之後,接下來說說這個題目的思路:

  • 線段樹的每一個區間表示這條邊的存在時間,每一個葉子節點表示一個詢問。
  • 一開始就存在的邊先用map存下來,之後的q次詢問,對於每次詢問的邊首先判斷是否這條邊,如果存在則把這條邊放入線段樹中,並刪除,不存在則存下來。
  • 最後每一條存下來的邊,都放入線段樹中。
  • 然後查詢,每次查詢類似於一個遞迴的過程,如果遍歷到的節點,如果存在邊,那麼並查集並起來,之後遍歷回來再刪除即可,這個遞迴的過程就可以求解。
#include <bits/stdc++.h>
#define debug(x) cout<<"debug:"<<#x<<" = "<<x<<endl;
using namespace std;
const int maxn = 5e5+10;
struct node{
    int u,v;
    node(int u=0,int v=0):u(u),v(v){}
};
int f[maxn],rk[maxn];
int Find(int x) {
    while (x != f[x]) x = f[x];
    return x;
}
node unite(int x,int y) {
    x = Find(x), y = Find(y);
    if (x == y) return 0;
    if (rk[x] > rk[y]) swap(x, y);
    f[x] = y;
    if (rk[x] == rk[y]) {
        rk[y]++;
        return node(x,rk[y]-1);
    }
    return node(x,rk[y]);
}
void del(int u,int v) {
    rk[f[u]] = v;
    f[u] = u;
}
vector<node>e[maxn<<2];
void update(int id,int l,int r,int x,int y,int u,int v){
    if(x<=l&&y>=r){
        e[id].push_back(node(u,v));
        return ;
    }
    int mid=(l+r)>>1;
    if(x<=mid) update(id<<1,l,mid,x,y,u,v);
    if(y>mid) update(id<<1|1,mid+1,r,x,y,u,v);
}

int ans[maxn];
int n, m, q;
void dfs(int id,int l,int r,int now) {
    for (int i = 0; i < e[id].size(); i++) {
        int u = e[id][i].u, v = e[id][i].v;
        if (Find(u) != Find(v)) --now;
        e[id][i] = unite(u, v);
    }
    if (l == r) ans[l] = now;
    else {
        int mid = (l + r) >> 1;
        dfs(id << 1, l, mid, now);
        dfs(id << 1 | 1, mid + 1, r, now);
    }
    for (int i = (int) e[id].size() - 1; i >= 0; i--) {
        int u = e[id][i].u,v = e[id][i].v;
        if (u) del(u,v);
    }
}
int cnt1[maxn],cnt2[maxn];
int edge1[maxn],edge2[maxn];
unordered_map<int,int>mp[maxn];
int main() {
    scanf("%d%d%d",&n,&m,&q);
    for (int i = 1; i <= n; i++) {
        int c;
        scanf("%d",&c);
        while (c--) {
            int x;
            scanf("%d",&x);
            mp[i][x] = 0;
            edge1[i]++,edge2[x]++;
        }
    }
    for(int i=1;i<=n+m;i++) f[i] = i;
    int tmp1 = 0, tmp2 = 0;
    for (int i = 1; i <= n; i++) if (edge1[i] == 0) tmp1++;
    for (int i = 1; i <= m; i++) if (edge2[i] == 0) tmp2++;
    for (int i = 1; i <= q; i++) {
        int y,x;
        scanf("%d%d",&y,&x);
        if (mp[x].count(y)) {
            update(1, 0, q, mp[x][y], i - 1, x, y + n);
            mp[x].erase(y);
            edge1[x]--, edge2[y]--;
            if (edge1[x] == 0) tmp1++;
            if (edge2[y] == 0) tmp2++;
        } else {
            mp[x][y] = i;
            if (edge1[x] == 0)tmp1--;
            if (edge2[y] == 0)tmp2--;
            edge1[x]++, edge2[y]++;
        }
        cnt1[i] = tmp1, cnt2[i] = tmp2;
    }
    for (int i = 1; i <= n; i++) {
        unordered_map<int, int>::iterator p;
        for (p = mp[i].begin(); p != mp[i].end(); p++) {
            update(1, 0, q, p->second, q, i, p->first + n);
        }
    }
    dfs(1, 0, q, n + m);
    for(int i=1;i<=q;i++){
//        printf("cnt1[%d]=%d cnt2[%d]=%d ans[%d]=%d\n",i,cnt1[i],i,cnt2[i],i,ans[i]);
        if(cnt2[i]) printf("-1\n");
        else  printf("%d\n",ans[i]-cnt1[i]);
    }
    return 0;
}