• 动态图连通性(线段树分治+按秩合并并查集)


    在考场上遇到了这个的板子题,,,所以来学习了一下线段树分治 + 带撤销的并查集。

    题目大意是这样的:有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
    ", 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
  • 相关阅读:
    深度学习之Python 脚本训练keras mnist 数字识别模型
    Hive udtf 报错 java.lang.String cannot be cast to java.lang.Integer
    vue 中 created 和 mounted 钩子生命周期 问题
    vue和 jsplumb 集成 出现下面的错误
    Cognos 中 javascript jQuery 的使用
    Hadoop 下常用的命令
    剑指offer30:连续子数组的最大和
    剑指offer29:最小的k个数
    剑指offer28:找出数组中超过一半的数字。
    剑指offer27:按字典序打印出该字符串中字符的所有排列
  • 原文地址:https://www.cnblogs.com/ww3113306/p/9896276.html
Copyright © 2020-2023  润新知