1. 程式人生 > >算法學習——動態圖連通性(線段樹分治+按秩合並並查集)

算法學習——動態圖連通性(線段樹分治+按秩合並並查集)

mes inline ret bsp getc class 離開 。。 node

在考場上遇到了這個的板子題,,,所以來學習了一下線段樹分治 + 帶撤銷的並查集。

題目大意是這樣的:有m個時刻,每個時刻有一個加邊or撤銷一條邊的操作,保證操作合法,沒有重邊自環,每次操作後輸出當前圖下所有聯通塊大小的乘積。

首先觀察到如果沒有撤銷操作,那麽直接用並查集就可以維護,每次合並的時候乘上要合並的兩個並查集大小的逆元,然後乘上合並之後的大小即可。

那麽來考慮撤銷,觀察到如果並查集不帶路徑壓縮,應該是可以處理撤銷操作的。

但我們並不能直接做,因為並查集的撤銷必須按順序來,就相當於每次合並的時候將一條邊壓入棧,撤銷的時候也只能從棧頂彈出。如果不按順序是維護不了的。

對於每個加邊操作而言,我們將它和離它最近的那個撤銷操作匹配(默認第m + 1個時刻有一個撤銷所有邊的操作)

假設加邊操作出現在第l個時刻,撤銷操作在第r個時刻,那麽對於這個二元組而言,它的作用是使得加入的那條邊在[l, r-1]的時間內出現。

於是我們考慮用線段樹來處理這個東西,我們可以將這條邊掛在線段樹上,相當於在線段樹上區間修改,將這條邊掛在[l, r -1]的區間上,

因此每條邊都會被拆分成log個區間,分別掛在線段樹上的對應位置,然後當我們經過線段樹上的一個節點時,我們就將這個節點上掛的邊都加入到當前圖中,相當於我們遍歷了整個線段樹,

線段樹上的每個葉子節點都是一個詢問(一個時刻),因此當我們遍歷到一個葉子節點時,我們就會擁有當前時刻應該擁有的邊。

當我們離開一個點時,我們就將在這個點上加入的邊撤銷,因為我們加邊是從上往下遍歷時一個一個加,而撤銷是回溯時一個一個撤銷,所以撤銷是按順序撤銷的,所以並查集就可以維護了。

感覺講的有一點混亂。。。。。

大概是一個區間上掛了一條邊表示這條邊在[l, r]中的所有時刻都出現了,當處理一個單點的時候,需要將到達這個單點的路徑上經過的所有區間中的邊都加入圖中,撤銷時按順序撤銷。

可能需要畫個圖之類的,應該還是比較好理解的。

技術分享圖片
  1 #include<bits/stdc++.h>
  2 using namespace std;
  3 #define R register int
  4 #define AC 101000
  5 #define ac 500000
  6 #define maxn 2010000
  7 #define p 1000000007
  8
#define LL long long 9 10 int n, m, top, cnt, w; 11 LL rnt = 1; 12 int Head[maxn], Next[maxn], date[maxn], tot; 13 int father[AC], up[AC]; 14 LL inv[AC], Size[AC], ans[AC]; 15 struct line{ 16 int x, y; 17 friend bool operator < (line a, line b) 18 { 19 if(a.x != b.x) return a.x < b.x; 20 else return a.y < b.y; 21 } 22 }road[AC]; 23 24 struct node{ 25 int fa, x; 26 }s[AC]; 27 28 map<line, int> MAP; 29 30 inline int read() 31 { 32 int x = 0;char c = getchar(); 33 while(c > 9 || c < 0) c = getchar(); 34 while(c >= 0 && c <= 9) x = x * 10 + c - 0, c = getchar(); 35 return x; 36 } 37 38 inline void add(int f, int w){ 39 date[++tot] = w, Next[tot] = Head[f], Head[f] = tot; 40 } 41 42 inline int find(int x){ 43 while(father[x] != x) x = father[x]; 44 return x; 45 } 46 47 inline void link(int &ret, int x, int y) 48 { 49 int fx = find(x), fy = find(y); 50 if(fx == fy) return ; 51 ++ ret;//只有連接了才要加ret 52 if(Size[fx] > Size[fy]) swap(fx, fy); 53 rnt = rnt * inv[Size[fx]] % p * inv[Size[fy]] % p; 54 father[fx] = fy, Size[fy] += Size[fx]; 55 s[++top] = (node){fy, fx}; 56 rnt = rnt * Size[fy] % p; 57 } 58 59 inline void cut() 60 { 61 node x = s[top --];//撤銷 62 rnt = rnt * inv[Size[x.fa]] % p; 63 father[x.x] = x.x, Size[x.fa] -= Size[x.x];//斷開連接 64 rnt = rnt * Size[x.fa] % p * Size[x.x] % p;//註意刪除一個點之後要把父親修改為自己,而不是0 65 } 66 67 void solve(int x, int l, int r)//當前區間編號,區間範圍 68 { 69 int now, ret = 0; 70 for(R i = Head[x]; i ; i = Next[i]) 71 { 72 now = date[i]; 73 link(ret, road[now].x, road[now].y); 74 } 75 int mid = (l + r) >> 1; 76 if(l == r) ans[l] = rnt; 77 if(l != r) solve(x * 2, l, mid), solve(x * 2 + 1, mid + 1, r); 78 for(R i = 1; i <= ret; i ++) cut(); 79 } 80 81 void pre() 82 { 83 n = read(), m = read(); 84 inv[0] = inv[1] = 1; 85 for(R i = 1; i <= n; i ++) father[i] = i, Size[i] = 1; 86 for(R i = 2; i <= n; i ++) 87 inv[i] = (p - p / i) * inv[p % i] % p; 88 } 89 90 void change(int x, int l, int r, int ll, int rr) 91 { 92 if(l == ll && r == rr){add(x, w); return ;} 93 int mid = (l + r) >> 1; 94 if(rr <= mid) change(x * 2, l, mid, ll, rr); 95 else if(ll > mid) change(x * 2 + 1, mid + 1, r, ll, rr); 96 else change(x * 2, l, mid, ll, mid), change(x * 2 + 1, mid + 1, r, mid + 1, rr); 97 } 98 99 void work() 100 { 101 int tmp; 102 for(R i = 1; i <= m; i ++) 103 { 104 int opt = read(), a = read(), b = read(); 105 if(a > b) swap(a, b); 106 if(!MAP[(line){a, b}]) 107 MAP[(line){a, b}] = ++ cnt, tmp = cnt, road[cnt] = (line){a, b}; 108 else tmp = MAP[(line){a, b}];//獲取編號 109 if(opt == 1) up[tmp] = i;//存下這條邊的出現時間 110 else w = tmp, change(1, 1, m, up[tmp], i - 1), up[tmp] = 0; 111 } 112 for(R i = 1; i <= cnt; i ++) 113 if(up[i]) w = i, change(1, 1, m, up[i], m); 114 solve(1, 1, m); 115 for(R i = 1; i <= m; i ++) printf("%lld\n", ans[i]); 116 } 117 118 int main() 119 { 120 //freopen("in.in", "r", stdin); 121 pre(); 122 work(); 123 //fclose(stdin); 124 return 0; 125 }
View Code

算法學習——動態圖連通性(線段樹分治+按秩合並並查集)