參考;http://blog.csdn.net/auto_ac/article/details/10672865
題意:點數n(n <= 30000), 邊數(m <= 90000)的圖,詢問 q(1<=q<=30000)。
對於每個詢問(l, r),去掉(l,r)區間以外的所有點和其相關聯的邊,問剩下來的圖的聯通塊的個數。
思路:分塊+並查集
分塊算法入門:http://blog.csdn.net/auto_ac/article/details/10050589
這題很容易想到分塊, 難點是並查集的處理。
對詢問離線分塊排序以後,我們對 左端點在相同塊號內的詢問 一起處理。這些詢問的右斷點是遞增的,
左端點在某個塊內,我們對每個詢問分成2個區域分別進行並查集處理,區域1:左端點所在的塊的區域(並查集tp)
(點的個數sqrt(n))。區域2:除區域1以外的其它詢問點(並查集f)。由於右斷點是遞增的,區域2很容易用一個並查集處理。區域1對於每個詢問,都重新處理一遍並查集。最後關鍵就是兩個並查集的合併,由於並查集不能進行刪除操作,我們需要做的是 利用並查集f裏面的信息而不破壞它,並且複雜度要符合題意。我們可以在維護並查集tp的同時讓這兩個並查集合並,即在並查集f中把我當前需要的點搬到並查集tp裏面,而沒有必要把並查集f全部搬過來,而且這樣做複雜度也不符合要求,用一個vis數組就可以做到
在實際處理的時候 並查集tp能處理到的點是整個詢問的區域內的點。
具體看代碼吧。
要清楚爲什麼要tp用的是f的。。
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <vector>
using namespace std;
#define pb push_back
const int maxn = 30004;
vector<int> edge[maxn];
int n, m;
int bsize;
struct node {
int l, r, b, no;//b塊號,no詢問編號
inline void in(int i) {
scanf("%d%d", &l, &r);
b = l/ bsize;
no = i;
}
bool operator <(const node &t) const { //先按左端點所在的塊號排,再按右端點排
if (b == t.b)
return r < t.r;
return b < t.b;
}
} q[maxn];
int cnt, sum; //cnt: 右邊並查集f的聯通塊個數, sum 左邊並查集tp的聯通塊個數
int f[maxn], tp[maxn];
int vis[maxn]; //vis[x]表示最後一次用到x的詢問是哪一個,可以記錄當前詢問用沒有把點x放過 處理左邊的並查集tp中
//R: 處理區域2, 即右邊區域的並查集
int Rfind(int x) {
return x == f[x] ? x : f[x] = Rfind(f[x]);
}
void Rmerge(int x, int y) {
int rx = Rfind(x), ry = Rfind(y);
if (rx == ry) return;
f[rx] = ry;
cnt--;
}
//L: 處理區域1, 即左邊區域的並查集
int cur;//當前的詢問號
int Lfind(int x) {
if (vis[x] != cur) {//判斷點x是否加入 處理左邊的並查集tp, 沒加入就加進來,更新vis
vis[x] = cur;
tp[x] = f[x];
}
return x == tp[x] ? x : tp[x] = Lfind(tp[x]) ;
}
void Lmerge(int x, int y) {
int rx = Lfind(x);
int ry = Lfind(y);
if (rx == ry) return;
tp[rx] = ry;
sum--;
}
int ans[maxn];
int R, B; //在當前詢問之前的詢問的右端點和塊號
int work(int l, int r, int b) {
int z = bsize * (b+1)-1, i, j; //z是左右邊區間的分界點,z點屬於左邊
if (B != b) { //新的一個塊的第一個詢問
R = z;
B = b;
cnt = 0;
for (i = z-bsize+1; i <= n; i++) //只要對f初始化,tp不用初始化(因爲用vis更新的緣故)。
f[i] = i;
}
//更新右邊並查集
for (i = max(R, z)+1; i <= r; i++) {
cnt++;
for (j = 0; j <(int) edge[i].size(); j++) {
int v = edge[i][j];
if (v > r || v <= z) continue;
Rmerge(i, v);
}
}
//更新左邊並查集
sum = 0;
for (i = l; i <= min(r, z); i++) {
sum++;
for (j = 0; j <(int) edge[i].size(); j++) {
int v = edge[i][j];
if (v > r || v < l) continue;
Lmerge(i, v);
}
}
R = r;
return sum+cnt;
}
void solve() {
int i, j, k;
R = B = -1;
for (i = 0; i < m; i++) {
j = i;
while (j < m && q[j].b == q[i].b) j++; //對於左端點在一個塊內的詢問一起處理
for (k = i; k < j; k++) {
cur = k;
ans[q[k].no] = work(q[k].l, q[k].r, q[k].b);
}
i = j - 1;
}
}
int main() {
int i, cas, ca = 1;
scanf("%d", &cas);
while (cas--) {
scanf("%d%d", &n, &m);
for(i = 1; i <= n; i++)
edge[i].clear();
while (m--) {
int x, y;
scanf("%d%d", &x, &y);
edge[x].pb(y);
edge[y].pb(x);
}
bsize = sqrt(n + 0.5);
scanf("%d", &m);
for (i = 0; i < m; i++) q[i].in(i);
sort(q, q + m);
memset(vis, -1, sizeof(vis));
solve();
printf("Case #%d:\n", ca++);
for(i = 0; i < m; i++)
printf("%d\n", ans[i]);
}
return 0;
}