洛谷P2542 [AHOI2005]航线规划

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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值