hdu 4677 并查集+分块算法 好题 (2013多校联合)

来源:互联网 发布:男生冬季服装搭配知乎 编辑:程序博客网 时间:2024/05/20 02:23

参考;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;      }  
原创粉丝点击