線段樹分治+可撤銷並查集 [2020牛客暑期多校訓練營(第八場)All-Star Game
阿新 • • 發佈:2020-08-05
線段樹分治+可撤銷並查集 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; }