Link-Cut-Tree维护双联通分量的典例,就当做再熟悉熟悉LCT吧
这题要求的实际上两点间桥的个数,易知,桥的个数等于连通块数减一
所以我们只需要维护这个就可以了
那么如何解决删边问题呢
我们消防水管局长一样,倒序处理,将删边改成加边即可
最重要的来了,如何达到维护连通块数的目标呢?
既然是在一棵树上加边一定会产生一个回路,这就是一个连通块
我们使用并查集,强行把这一棵splay缩成一个点,就可以了
简述一下新的加边过程
一、判断是否已经缩成一个点,如果是直接返回
二、判断是否在一棵子树中,若不在,直接Link
三、使用split拉出x->y这一条链,强行缩点
听起来很简单,然而细节多的让人头疼
主要讲一下和LCT不一样的地方
看代码吧
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cstring>
#include<stack>
#include<queue>
#define lch(x) ch[x][0]
#define rch(x) ch[x][1]
using namespace std;
const int maxn = 30007;
int n,m,ch[maxn][2],lazy[maxn],f[maxn],h[maxn],s[maxn],bj[maxn*20],workA[maxn*20];
int workB[maxn*20],del[maxn*20],tot=0,ans[maxn*20];
struct node{
int x,y;
friend bool operator <(const node a,const node b){
return a.x==b.x?a.y<b.y:a.x<b.x;
}
}g[maxn*20];// 效仿FlashHu大佬,排序后使用lower_bound优化查找
int find(int x){ return h[x]==x?x:h[x]=find(h[x]); }
bool notroot(int x){return ch[f[x]][1]==x||ch[f[x]][0]==x;}
void pushup(int k){s[k]=s[lch(k)]+s[rch(k)]+1;}// 联通块数量
void pushdown(int k){
if(lazy[k]){
swap(lch(k),rch(k));
lazy[lch(k)]^=1;
lazy[rch(k)]^=1;
lazy[k]=0;
}
}
void rotate(int x)
{
int y=f[x],z=f[y],k=ch[y][1]==x,w=ch[x][!k];
if(notroot(y))ch[z][ch[z][1]==y]=x;ch[x][!k]=y;ch[y][k]=w;
if(w)f[w]=y;f[y]=x;f[x]=z;
pushup(y);
}
int st[maxn*20];
void splay(int x){
int cnt=0,x1=x,y,z;
st[++cnt]=x1;
while(notroot(x1))st[++cnt]=x1=f[x1];
while(cnt)pushdown(st[cnt--]);
while(notroot(x)){
y=f[x],z=f[y];
if(notroot(y))
rotate((ch[z][0]==y)^(ch[y][0]==x)?x:y);
rotate(x);
}
pushup(x);
return ;
}
void access(int k){
for(int y=0;k;y=k,k=f[y]=find(f[k])){// 因为缩点的原因,所以往上拉的时候不可以只找父
splay(k);rch(k)=y;pushup(k);// 亲,而是要找到他的并查集祖先,不然没有意义
}
}
void makeroot(int x){
access(x);
splay(x);
lazy[x]^=1;
}
void split(int x,int y){
makeroot(y);
access(x);
splay(x);
}
int findroot(int x){
access(x),splay(x);
pushdown(x);
while(lch(x))pushdown(x=lch(x));
splay(x);
return x;
}
void out(int now,int ance){if(now)h[now]=ance,out(lch(now),ance),out(rch(now),ance);}
// 在这一棵子树中,把所有的点的并查集祖先改成根,达成缩点目的
void merge(int x,int y){
x=find(x),y=find(y);
if(x==y)return ;// 缩过点了,返回
makeroot(x);
if(findroot(y)==x){// 在一棵子树中,缩成一个点
out(rch(x),x);
rch(x)=0;
pushup(x);// 更新信息,现在它只有一个联通块了,因为缩成一个点了
}
else f[x]=y; // 正常Link,合法性上面判断过了
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)h[i]=i,s[i]=1;
for(int i=1;i<=m;i++){
int x,y;
scanf("%d%d",&x,&y);
if(x>y)swap(x,y);// 严格从小到大,方便查找
g[i].x=x;g[i].y=y;
}
sort(g+1,g+1+m); // 排序后方便找边
int A,B,C,cnt=0;
scanf("%d",&C);
while(C!=-1){
scanf("%d%d",&A,&B);
cnt++;
if(A>B)swap(A,B);
if(C==0){
bj[cnt]=0;// 加边标记
del[lower_bound(g+1,g+1+m,(node){A,B})-g]=1;// 该边已删除
}
else bj[cnt]=1;
workA[cnt]=A,workB[cnt]=B;
scanf("%d",&C);
}
for(int i=1;i<=m;i++){
if(!del[i])merge(g[i].x,g[i].y);// 未被标记,按照规则连边
}
for(;cnt;cnt--){
int fx=find(workA[cnt]);
int fy=find(workB[cnt]);
if(bj[cnt]){
split(fx,fy);// 拉出这一条链,统计信息
ans[++tot]=s[fx]-1;
}
else{
merge(fx,fy);// 不然连边
}
}
while(tot>0)printf("%d\n",ans[tot--]);// 输出答案
return 0;
}