CF1416D Graph and Queries (并查集 重构树 线段树)
给定一个 n n n 个点 m m m 条边的无向图,第 i i i 个点的点权初始值为 p i p_i pi , 所有 p i p_i pi 互不相同。
接下来进行 q q q 次操作,分为两类:
- 1 v \tt 1\ v 1 v 查询与 v v v 连通的点中, p u p_u pu 最大的点 u u u 并输出 p u p_u pu,然后让 p u = 0 p_u=0 pu=0。
- 2 i \tt 2\ i 2 i 将第 i i i 条边删掉。
删边操作肯定不好处理,还是要考虑将操作离线下来,转删边操作为加边操作。但是有一个问题,就是倒序处理的话,没办法知道哪些点已经被访问过点权变成 0 .
于是我们希望在能够得知任意时刻结点连通情况的前提下,顺着询问顺序处理操作 1 。也就是说我们逆着时间线根据操作 2 预处理每个时刻结点的连通情况,然后顺着时间线处理操作 1.
这里运用了建立重构树的技巧。
先将所有被删除的边打上标记,然后合并没有被删除的边两端的结点,得到最后时刻的结点连通情况。

然后逆着时间线处理被删除的边,当加入这条边会影响图中某些结点的连通情况时,新建一个虚点,连向会影响到的两个连通块,点权为 0 .如果碰到操作 1 ,那就查询一下, v v v 所在连通块的树根,并记录下来。

完成以上操作后,我们会得到一棵树,然后我们对这棵树进行 DFS ,以 DFS 序作为定义域,那么就能方便用线段树维护子树中的最大权值。
回到正序的时间线,当我们碰到操作 1 的时候,就可以用线段树查询 v v v 所在的连通块的树根的子树中的最大权值,并且修改权值即可。
本题的妙处在于:
- 删边转为加边的套路。普通的并查集并不支持删边操作,所以这种套路常常使用。
- 建立虚点用于表达关键时刻的连通情况。一个虚点连接了两个将要分离的连通块,在分离之前是虚点维护了连通性,也作为了连通块的树根,便于查询。
- 虚点带有时间性质。当一个虚点作为另一个虚点的儿子的时候,它就失去了树根的性质,表达了其子树与别的连通块在早些时候连通的信息。
- 以 DFS 序作为线段树的定义域,树根的 DFS 序与子树中结点的 DFS 序一定是连续的,所以就可以用线段树维护子树中的信息。轻重链剖分也是相似的原理,通过处理出一些连续的信息,可以用线段树维护树上的信息。
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int MAX_N = 200000;
const int MAX_M = 300000;
const int MAX_Q = 500000;
int N,M,Q;
int w[MAX_N + MAX_Q + 5],head[MAX_N + MAX_Q + 5],cnt,fa[MAX_N + MAX_Q + 5],siz[MAX_N + MAX_Q + 5];
int x[MAX_M + 5],y[MAX_M + 5],opt[MAX_Q + 5],z[MAX_Q + 5],quest[MAX_Q + 5],xx,yy;
int dfncnt , dfn[MAX_N + MAX_Q + 5],lt[MAX_N + MAX_Q + 5],rt[MAX_N + MAX_Q + 5],rev[MAX_N + MAX_Q + 5];
int t[(MAX_N + MAX_Q + 5) << 2],tr[(MAX_N + MAX_Q + 5) << 2],tot;
bool del[MAX_M + 5];
pair<int ,int > ans;
struct edge{
int nxt;
int to;
}e[MAX_M + MAX_Q + 5];
void Addedge(int from,int to)
{
cnt ++;
e[cnt].nxt = head[from];
e[cnt].to = to;
head[from] = cnt;
return ;
}
int Find(int x)//并查集
{
return (fa[x] == x ? x : (fa[x] = Find(fa[x])));//只要不影响虚点对子树的统治,可以路径压缩
}
void Join(int x,int y)//并查集部分
{
int rx = Find(x);int ry = Find(y);
if(rx == ry) return ;
// if(siz[rx] > siz[ry]) swap(rx,ry);
fa[rx] = ry;Addedge(ry,rx);
return ;
}
void DFS(int u)//根据 DFS 序编号
{
dfncnt ++;dfn[u] = dfncnt;lt[u] = dfncnt;rev[dfncnt] = u;
for(int i = head[u],v = 0;i;i = e[i].nxt)
{
v = e[i].to;
DFS(v);
}
rt[u] = dfncnt;
return ;
}
void Build(int k,int l,int r)
{
if(l == r)
{
t[k] = w[rev[l]];//用 DFS 序的编号构造维护最大权值的线段树
tr[k] = rev[l];
return ;
}
int mid = (l + r) >> 1;int lson = k << 1;int rson = lson + 1;
Build(lson,l,mid);
Build(rson,mid + 1,r);
if(t[lson] > t[rson])
{
t[k] = t[lson];
tr[k] = tr[lson];
}
else
{
t[k] = t[rson];
tr[k] = tr[rson];
}
return ;
}
pair<int ,int > Query(int k,int l,int r,int x,int y)
{
if(l > y || r < x) return make_pair(0,0);
if(x <= l && r <= y) return make_pair(t[k],tr[k]);
int mid = (l + r) >> 1;int lson = k << 1;int rson = lson + 1;
pair<int ,int > res,tmp;
tmp = Query(lson,l,mid,x,y);
res = Query(rson,mid + 1,r,x,y);
return (tmp.first > res.first ? tmp : res );
}
void Modify(int k,int l,int r,int x)
{
if(l > x || r < x) return ;
if(l == r && l == x)
{
t[k] = 0;
return ;
}
int mid = (l + r) >> 1;int lson = k << 1;int rson = lson + 1;
Modify(lson,l,mid,x);
Modify(rson,mid + 1,r,x);
if(t[lson] > t[rson])
{
t[k] = t[lson];
tr[k] = tr[lson];
}
else
{
t[k] = t[rson];
tr[k] = tr[rson];
}
return ;
}
int main()
{
scanf("%d%d%d",&N,&M,&Q);tot = N;
for(int i = 1; i <= N;i ++) scanf("%d",&w[i]);
for(int i = 1;i <= N;i ++) fa[i] = i,siz[i] = 1;
for(int i = 1;i <= M;i ++) scanf("%d%d",&x[i],&y[i]);
for(int i = 1;i <= Q;i ++)
{
scanf("%d%d",&opt[i],&z[i]);
if(opt[i] == 2) del[z[i]] = 1;
}
for(int i = 1;i <= M;i ++)
if(!del[i]) Join(x[i],y[i]);//将所有没删的边连上
for(int i = Q;i >= 1;i --)
{
if(opt[i] == 2)
{
xx = Find(x[z[i]]);yy = Find(y[z[i]]);//
if(xx != yy)//影响连通性
{
tot ++;fa[tot] = tot;//建立一个虚点
fa[xx] = tot;fa[yy] = tot;
Addedge(tot,xx);Addedge(tot,yy);
}
}
else
{
quest[i] = Find(z[i]);//获取当前连通块的根,知道询问时结点在哪个连通块内
}
}
for(int i = 1;i <= tot;i ++)
if(dfn[i] == 0)
{
xx = Find(i);
DFS(xx);
}
Build(1,1,dfncnt);
for(int i = 1;i <= Q;i ++)
if(opt[i] == 1)
{
ans = Query(1,1,dfncnt,lt[quest[i]],rt[quest[i]]);//回答询问
printf("%d\n",ans.first);
if(ans.second > 0) Modify(1,1,dfncnt,dfn[ans.second]);//修改操作
}
return 0;
}
11万+

被折叠的 条评论
为什么被折叠?



