算法学习——动态图连通性(线段树分治+按秩合并并查集)

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

题目大意是这样的:有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 }

原文地址:https://www.cnblogs.com/ww3113306/p/9896276.html

时间: 2024-10-12 04:30:18

算法学习——动态图连通性(线段树分治+按秩合并并查集)的相关文章

Codeforces 1140F Extending Set of Points 线段树 + 按秩合并并查集 (看题解)

Extending Set of Points 我们能发现, 如果把x轴y轴看成点, 那么答案就是在各个连通块里面的x轴的个数乘以y轴的个数之和. 然后就变成了一个并查集的问题, 但是这个题目里面有撤销的操作, 所以我们要把加入和撤销操作变成 这个点影响(L , R)之间的询问, 然后把它丢到线段树里面分成log段, 然后我们dfs一遍线段树, 用按秩合并并查集取维护, 回溯的时候将并查集撤销. #include<bits/stdc++.h> #define LL long long #def

线段树分治总结

目录 类型一 例题1:八纵八横 代码: 例题2:时空旅行 首先,要求可以离线. 线段树分治有两种. 类型一 操作基于区间,单点询问. 有时,进行的一种操作可以快速完成,但是,要实现这种操作的逆操作较难. 因为,通常情况下,需要实现的逆操作都是很久以前执行的. 但是,如果只撤销上次操作,就会简单得多. 比如,维护一些连通性,或直径,线性基等问题. 这类问题加边很好做,但删边很难实现. 我们可以扫一遍操作,得到每个操作的有效区间. 然后,将每个添加操作的有效区间按在线段树上,然后遍历这颗线段树同时处

线段树分治总结(线段树分治,线段树,并查集,树的dfn序,二分图染色)

闲话 stO猫锟学长,满脑子神仙DS 线段树分治思想 我们在做CDQ的时候,将询问和操作通通视为元素,在归并过程中统计左边的操作对右边的询问的贡献. 而在线段树分治中,询问被固定了.按时间轴确定好询问的序列以后,我们还需要所有的操作都会影响一个时间区间.而这个区间,毫无疑问正好对应着询问的一段区间. 于是,我们可以将每一个操作丢到若干询问里做区间修改了,而线段树可以高效地维护.我们开一个叶子节点下标为询问排列的线段树,作为分治过程的底层结构. 具体的实现,仍然要看题目. 例题1 BZOJ4025

【线段树分治 线性基】luoguP3733 [HAOI2017]八纵八横

不知道为什么bzoj没有HAOI2017 题目描述 Anihc国有n个城市,这n个城市从1~n编号,1号城市为首都.城市间初始时有m条高速公路,每条高速公路都有一个非负整数的经济影响因子,每条高速公路的两端都是城市(可能两端是同一个城市),保证任意两个城市都可以通过高速公路互达. 国正在筹划“八纵八横”的高铁建设计划,计划要修建一些高速铁路,每条高速铁路两端也都是城市(可能两端是同一个城市),也都有一个非负整数的经济影响因子.国家还计划在“八纵八横”计划建成之后,将“一带一路”扩展为“一带_路一

3237: [Ahoi2013]连通图 线段树分治

题解: 线段树分治裸题 apio t1是这个所以就学习了一下 #include <bits/stdc++.h> using namespace std; const int N=2e5+10; struct re{ int x,y; }a[N*2]; int b[N+20][5],cnt,now,n,m,k; int ls[N*15],rs[N*15],data[N*15],last[N+20]; int ph[N*4],pt[N*4],count2[N+20],f[N]; bool ft[N

HAOI2017 八纵八横——线段树分治+线性基

题目大意 给定一个图,每次加一些边,或者删掉一些后来加上去的边,定义一个环的价值为环上所有的边的异或和,重复走的边重复算.每次询问这个时刻图中的所有经过1号点的环的最大价值. 思路 首先考虑对于一个静态的图如何求解图中所有经过1号点的环的最大价值,发现这个经过1号点就是唬人的,图中任意一个环都可以经过1号点再走回来. 于是题目变成了求解图中环的最大价值,可以将图中所有的简单环给拎出来放到线性基里面求最大价值,不难发现这是对的. 然后题目转化为了如何求图中所有的简单环,一般我们可以直接对图dfs找

线段树分治

2014徐寅展论文<线段树在一类分治问题上的应用>读后感. 线段树分治 线段树分治其实就是有撤销操作的时间分治. 题目让你维护一些信息,每次可以询问,可以执行一种操作,也可以将之前的某个这种操作撤回. 操作容易维护,但撤回操作不容易维护. 需要将操作,询问都离线下来.将时间轴画出来,那么每个操作只在时间轴上的一个区间内生效. 用线段树给这个区间打上这个操作的标记,维护信息. TJOI2018 数学计算 小豆现在有一个数x,初始值为1. 小豆有Q次操作,操作有两种类型: m: x = x * m

[HDU4867]Xor (线段树分治+类数位dp)

[HDU4867]Xor (线段树分治+类数位dp) 提供一种\((m+n) log a log m\)带有常数约\(\frac{1}{log n}\)的算法 处理询问,将后来加入的数算进序列中,则每个数\(a_i\)都有一段出现的区间\([L,R]\) 离线询问后,我们考虑用线段树分治将这些数加入到询问区间上 由于最多只有5000个修改操作,事实上这些数在线段树上覆盖的区间最多只有\(10000logm\)个,并且有着极其不满的常数(因为每个位置上的数都由多段区间组合而来,总长为\(m\),或

【BZOJ-4636】蒟蒻的数列 动态开点线段树 ||(离散化) + 标记永久化

4636: 蒟蒻的数列 Time Limit: 30 Sec  Memory Limit: 256 MBSubmit: 247  Solved: 113[Submit][Status][Discuss] Description 蒟蒻DCrusher不仅喜欢玩扑克,还喜欢研究数列 题目描述 DCrusher有一个数列,初始值均为0,他进行N次操作,每次将数列[a,b)这个区间中所有比k小的数改为k,他想知 道N次操作后数列中所有元素的和.他还要玩其他游戏,所以这个问题留给你解决. Input 第一