专栏文章
题解:P5247 【模板】动态图连通性
P5247题解参与者 5已保存评论 5
文章操作
快速查看文章及其快照的属性,并进行相关操作。
- 当前评论
- 5 条
- 当前快照
- 1 份
- 快照标识符
- @miqfb507
- 此快照首次捕获于
- 2025/12/04 03:52 3 个月前
- 此快照最后确认于
- 2025/12/04 03:52 3 个月前
Holm-de Lichtenberg-Thorup 算法
听了 WC 讲课来写一篇题解,文末会附上实现。下文认为 同级。
ETT
我们首先需要找个数据结构支持这样一个维护问题:
-
连接点 。保证两点不连通。
-
断开边 ,保证这条边存在。
-
查询 所在连通块内所有点的半群信息。
我们定义一棵树的欧拉序为,从某个点出发 dfs,将走过的所有边按照顺序写下来,然后对于每个点 的任意两条形如 , 的边之间插入边 。最后形成的序列。
容易发现欧拉序是一个环,从哪里开始都无所谓。
考虑使用 FHQtreap 维护欧拉序,对于 FHQtreap 的每个点记录一个父亲以用来找根。一张图会是一个 FHQtreap 森林,一棵 FHQtreap 代表了一棵树。查询一个点所在树的半群信息只需要在 FHQtreap 上合并信息即可。
接下来讲一下如何在进行上述操作时使用 FHQtreap 维护每棵树的任意一个欧拉序。
makeroot
首先需要一个辅助操作,换根,实际上我们维护的树本身是无根的,但是如果令一个 的边为欧拉序的第一条边,那么这个欧拉序则可以视为从 开始。
实际上需要在 FHQtreap 上做的操作就是在 处裂开然后把前面接在后面就行。
link
首先把需要连接的两个点 分别做一次 makeroot 操作。
然后新树的欧拉序可以视为 所在树的欧拉序,, 所在树的欧拉序, 依次拼接。
cut
把欧拉序序列在 两处断开,两条边丢掉后,第一个部分与第三个部分合并即可,容易发现这就是 link 的逆操作。
上述所有操作复杂度都是 FHQtreap 的单次操作复杂度,为 。
Holm-de Lichtenberg-Thorup 算法
考虑给每条边都赋一个等级 。
定义 表示所有等级大于等于 的边构成的边集。
定义 为 的一个极大生成森林。
我们在维护过程中保证任意 为 的一个子集,任意 为 的一个子集,且 中任意一个连通块大小不超过 。
容易发现 ,不然会违反 为 的极大生成森林的性质。
link
将边 加入 ,如果 在 中不连通,则将其在 中连边。令 。
cut
考虑假若边 不在 中,其不会在任意 中,把其在 中全部删去,不会对连通性产生任何影响,故直接删去即可。
否则先将其从 中全部删去,然后从 开始依次尝试找一条非树边连接因为 被删去而分裂出的两个连通块,不妨令这两个连通块为 。
首先,我们不可能用一条等级大于 的边来连接 ,因为如果存在这样的边 ,那么在图 中不存在边 而存在边 ,此时 应该处于 中,而 为 的子集,故 中也存在边 ,那么在边 删去前 就不是一棵树了。
当你在图 中尝试找一条非树边连接 时,由于任意图 中的非树边都已经被尝试过或者被上面证明了不可行,所以我们就只尝试等级为 的非树边,考虑遍历 中所有存在 级非树边的点,遍历这些点的 级非树边,显然这条边的另一个端点要么属于 要么属于 ,假若属于其他部分则会违反 在边 断开之前是一个极大生成森林的性质,假若遍历到的边属于 则给他升级为 级边,否则你就找到了应该连上的边,将这条边从非树边集合中删除后作为树边插入 即可结束流程。不妨钦定 ,由于 ,所以 ,所以我们即使将 中的 级边全部升级为 级边也没问题。当然为了防止升级的边在 中成为树边而在 中不为树边,先要把 中所有 级树边升为 级边。假若这个流程结束还没有找到想要的边,则去向 递归找边。
分析下复杂度,你会以 的代价将 条边升级,而总的升级次数是 故总的复杂度为 。
实现细节
我们需要快速在 中找到一条 级树边和一个有 级非树边的点,考虑使用 ETT 维护连通块中编号最小的 级树边和编号最小的存在 级非树边的点即可。
CPP#include<bits/stdc++.h>
#include<bits/extc++.h>
using namespace std;
const int maxn = 5e3+14;
const int maxk = 13;
const int warma = 12;
const int inf = 1e9;
namespace IO{
const int SIZE=1<<21;
static char ibuf[SIZE],obuf[SIZE],*iS,*iT,*oS=obuf,*oT=oS+SIZE-1;
int qr;
char qu[55],c;
bool f;
#define getchar() (IO::iS==IO::iT?(IO::iT=(IO::iS=IO::ibuf)+fread(IO::ibuf,1,IO::SIZE,stdin),(IO::iS==IO::iT?EOF:*IO::iS++)):*IO::iS++)
#define putchar(x) *IO::oS++=x,IO::oS==IO::oT?flush():0
#define flush() fwrite(IO::obuf,1,IO::oS-IO::obuf,stdout),IO::oS=IO::obuf
#define puts(x) IO::Puts(x)
template<typename T>
inline void read(T&x){
for(f=1,c=getchar();c<48||c>57;c=getchar())f^=c=='-';
for(x=0;c<=57&&c>=48;c=getchar()) x=(x<<1)+(x<<3)+(c&15);
x=f?x:-x;
}
template<typename T>
inline void write(T x){
if(!x) putchar(48); if(x<0) putchar('-'),x=-x;
while(x) qu[++qr]=x%10^48,x/=10;
while(qr) putchar(qu[qr--]);
}
inline void Puts(const char*s){
for(int i=0;s[i];++i)
putchar(s[i]);
putchar('\n');
}
struct Flusher_{~Flusher_(){flush();}}io_flusher_;
}
using IO::read;
using IO::write;
struct my_hash{
static uint64_t splitmix64(uint64_t x) {
x += 0x9e3779b97f4a7c15;
x = (x ^ (x >> 30)) * 0xbf58476d1ce4e5b9;
x = (x ^ (x >> 27)) * 0x94d049bb133111eb;
return x ^ (x >> 31);
}
size_t operator()(uint64_t x) const {
static const uint64_t FIXED_RANDOM =
chrono::steady_clock::now().time_since_epoch().count();
return splitmix64(x + FIXED_RANDOM);
}
size_t operator()(pair<uint64_t, uint64_t> x) const {
static const uint64_t FIXED_RANDOM =
chrono::steady_clock::now().time_since_epoch().count();
return splitmix64(x.first + FIXED_RANDOM) ^
(splitmix64(x.second + FIXED_RANDOM) >> 1);
}
};
unordered_set<int,my_hash> E[maxn][maxk];//(u.v) level-i
mt19937 rd(time(0));
__gnu_pbds::gp_hash_table<int,int,my_hash> lev[maxn];
int n,m;
class ETT{
private:
int k;
int rk[maxn*3],ls[maxn*3],rs[maxn*3],fa[maxn*3],sz[maxn*3],siz[maxn*3];
int minpos[maxn*3];
pair<int,pair<int,int> > minedge[maxn*3],w[maxn*3];
int tot;
__gnu_pbds::gp_hash_table<int,int,my_hash> id;
vector<int> vec;
void pushup(int cur);//FHQtreap
int merge(int u,int v);//FHQtreap
void split(int cur,int K,int &l,int &r);//FHQtreap
int clone();
int rank(int u);//ETT
void makeroot(int u);//tree
int del_left(int u);
public:
//tree
void init(int level);
bool link(int u,int v);
void cut(int u,int v);
void upd(int u);
int asksz(int u);
int find(int u);
int findroot(int u);//ETT
void update(int u,int v);
pair<int,pair<int,int> > findedge(int u);
}tr[maxk];
int ETT::del_left(int u){
if(ls[u]==0){
u=rs[u];
pushup(u);
return u;
}else{
ls[u]=del_left(ls[u]);
if(ls[u]!=0) fa[ls[u]]=u;
pushup(u);
return u;
}
}
void ETT::pushup(int cur){
if(cur==0) return ;
sz[cur]=sz[ls[cur]]+sz[rs[cur]]+(cur<=n);
siz[cur]=siz[ls[cur]]+siz[rs[cur]]+1;
minpos[cur]=min(minpos[ls[cur]],minpos[rs[cur]]);
minedge[cur]=min(w[cur],min(minedge[ls[cur]],minedge[rs[cur]]));
if(cur<=n&&E[cur][k].size()>0) minpos[cur]=min(minpos[cur],cur);
}
int ETT::merge(int u,int v){
if(u==0||v==0) return u+v;
if(rk[u]>rk[v]){
rs[u]=merge(rs[u],v);
pushup(u);
if(rs[u]!=0) fa[rs[u]]=u;
return u;
}else{
ls[v]=merge(u,ls[v]);
pushup(v);
if(ls[v]!=0) fa[ls[v]]=v;
return v;
}
}
void ETT::split(int cur,int K,int &l,int &r){
if(cur==0){
l=r=0;
return ;
}
if(K>=siz[ls[cur]]+1){
l=cur;
split(rs[l],K-(siz[ls[cur]]+1),rs[l],r);
if(rs[l]!=0) fa[rs[l]]=l;
pushup(l);
return ;
}else{
r=cur;
split(ls[r],K,l,ls[r]);
if(ls[r]!=0) fa[ls[r]]=r;
pushup(r);
return ;
}
}
int ETT::clone(){
if(vec.size()==0){
tot++;
minpos[tot]=inf;
siz[tot]=1;
sz[tot]=0;
rk[tot]=rd();
ls[tot]=rs[tot]=fa[tot]=0;
w[tot]=minedge[tot]=make_pair(inf,make_pair(inf,inf));
return tot;
}
else{
int u=vec.back();
vec.pop_back();
minpos[u]=inf;
siz[u]=1;
rk[u]=rd();
sz[u]=0;
ls[u]=rs[u]=fa[u]=0;
w[u]=minedge[u]=make_pair(inf,make_pair(inf,inf));
return u;
}
}
int ETT::rank(int u){
int res=siz[ls[u]];
while(fa[u]!=0){
if(u==rs[fa[u]]) res+=siz[ls[fa[u]]]+1;
u=fa[u];
}
return res;
}
void ETT::makeroot(int u){
int rt=findroot(u);
int K=rank(u);
int x=0,y=0;
split(rt,K,x,y);
fa[x]=fa[y]=0;
rt=merge(y,x);
}
void ETT::init(int level){
k=level;
tot=n;
minpos[0]=inf;
minedge[0]=w[0]=make_pair(inf,make_pair(inf,inf));
ls[0]=rs[0]=fa[0]=0;
for(int i=1;i<=n;i++){
sz[i]=1;
siz[i]=1;
ls[i]=rs[i]=fa[i]=0;
minpos[i]=inf;
rk[i]=rd();
minedge[i]=w[i]=make_pair(inf,make_pair(inf,inf));
}
}
bool ETT::link(int u,int v){
if(u>v) swap(u,v);
if(findroot(u)==findroot(v)) return false;
makeroot(u);
makeroot(v);
int e1=id[u*(maxn*3)+v]=clone();
int e2=id[v*(maxn*3)+u]=clone();
minedge[e1]=w[e1]=make_pair(lev[u][v],make_pair(u,v));
int x=findroot(u),y=findroot(v);
int rt=merge(x,merge(e1,merge(y,e2)));
return true;
}
void ETT::cut(int u,int v){
int e1=id[u*(maxn*3)+v],e2=id[v*(maxn*3)+u];
int l=rank(e1),r=rank(e2);
if(l>r) swap(l,r);
int x=0,y=0,z=0;
int rt=findroot(u);
split(rt,l,x,y);
fa[x]=fa[y]=0;
r-=l;
y=del_left(y);
fa[y]=0;
r--;
split(y,r,y,z);
fa[y]=fa[z]=0;
z=del_left(z);
fa[z]=0;
//x y z
vec.push_back(e1);
vec.push_back(e2);
id.erase(e1);
id.erase(e2);
rt=merge(x,z);
return ;
}
void ETT::upd(int u){
pushup(u);
while(fa[u]!=0) u=fa[u],pushup(u);
}
int ETT::asksz(int u){
return sz[findroot(u)];
}
int ETT::find(int u){
return minpos[findroot(u)];
}
int ETT::findroot(int u){
while(fa[u]!=0){
u=fa[u];
}
return u;
}
void ETT::update(int u,int v){
if(u>v) swap(u,v);
int x=id[u*(maxn*3)+v];
w[x]=make_pair(lev[u][v],make_pair(u,v));
pushup(x);
while(fa[x]!=0) x=fa[x],pushup(x);
}
pair<int,pair<int,int> > ETT::findedge(int u){
return minedge[findroot(u)];
}
void link(int u,int v){
if(tr[0].link(u,v)==false){
E[u][0].insert(v);
E[v][0].insert(u);
tr[0].upd(u);
tr[0].upd(v);
}
lev[u][v]=lev[v][u]=0;
}
void insert(int u,int v,int k){
//insert (u,v) E_k
if(tr[k].link(u,v)==false){
E[u][k].insert(v);
E[v][k].insert(u);
tr[k].upd(u);
tr[k].upd(v);
}
}
void improve(int u,int v){
E[u][lev[u][v]].erase(v);
E[v][lev[u][v]].erase(u);
tr[lev[u][v]].upd(u);
tr[lev[u][v]].upd(v);
lev[u][v]++,lev[v][u]++;
insert(u,v,lev[u][v]);
}
void cut(int u,int v){
int level=lev[u][v];
if(E[u][level].find(v)==E[u][level].end()){
//(u,v) is tree_edge
for(int i=level;i>=0;i--){
tr[i].cut(u,v);
}
for(int i=level;i>=0;i--){
if(tr[i].asksz(u)>tr[i].asksz(v)) swap(u,v);
pair<int,pair<int,int> > res;
while((res=tr[i].findedge(u)).first==i){
int x=res.second.first,y=res.second.second;
lev[x][y]++;
lev[y][x]++;
tr[i].update(x,y);
insert(x,y,lev[x][y]);
}
//|T(u)|<=|T(v)|
int x;
while((x=tr[i].find(u))!=inf){
while(E[x][i].size()>0){
int y=(*E[x][i].begin());
if(tr[i].findroot(x)==tr[i].findroot(y)){
improve(x,y);
}else{
E[x][i].erase(y);
E[y][i].erase(x);
tr[i].upd(x);
tr[i].upd(y);
for(int j=i;j>=0;j--) tr[j].link(x,y);
lev[u].erase(v),lev[v].erase(u);
return ;
}
}
}
}
}else{
E[u][level].erase(v);
E[v][level].erase(u);
tr[level].upd(u);
tr[level].upd(v);
}
lev[u].erase(v),lev[v].erase(u);
}
int lst;
int main(){
read(n);
read(m);
for(int i=0;i<=warma;i++) tr[i].init(i);
while(m--){
int op,x,y;
read(op);
read(x);
read(y);
x^=lst;
y^=lst;
if(op==0){
link(x,y);
}else if(op==1){
cut(x,y);
}else{
if(tr[0].findroot(x)==tr[0].findroot(y)){
lst=x;
putchar('Y');
}else{
lst=y;
putchar('N');
}
putchar('\n');
}
}
return 0;
}
相关推荐
评论
共 5 条评论,欢迎与作者交流。
正在加载评论...