[AHOI2005]【bzoj1969】LANE 航线规划——LCT维护双联通分量

本文介绍了一道关于在Samuel星系中维护不同星球间探险航线的问题。利用LCT(链剖树)和边双联通分量等数据结构和技术,解决了在航线变化时快速更新关键航线数量的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前置技能点:LCT,双联通分量

如果你不知道上面的东西,请先行了解

start_of_题面

对Samuel星球的探险已经取得了非常巨大的成就,于是科学家们将目光投向了Samuel星球所在的星系——一个巨大的由千百万星球构成的Samuel星系。

星际空间站的Samuel II巨型计算机经过长期探测,已经锁定了Samuel星系中许多星球的空间坐标,并对这些星球从1开始编号1、2、3……。

一些先遣飞船已经出发,在星球之间开辟探险航线。

探险航线是双向的,例如从1号星球到3号星球开辟探险航线,那么从3号星球到1号星球也可以使用这条航线。

例如下图所示:

这里写图片描述

在5个星球之间,有5条探险航线。

A、B两星球之间,如果某条航线不存在,就无法从A星球抵达B星球,我们则称这条航线为关键航线。

显然上图中,1号与5号星球之间的关键航线有1条:即为4-5航线。

然而,在宇宙中一些未知的磁暴和行星的冲撞,使得已有的某些航线被破坏,随着越来越多的航线被破坏,探险飞船又不能及时回复这些航线,可见两个星球之间的关键航线会越来越多。

假设在上图中,航线4-2(从4号星球到2号星球)被破坏。此时,1号与5号星球之间的关键航线就有3条:1-3,3-4,4-5。

小联的任务是,不断关注航线被破坏的情况,并随时给出两个星球之间的关键航线数目。现在请你帮助完成。

Input

第一行有两个整数N,M。表示有N个星球(1< N < 30000),初始时已经有M条航线(1 < M < 100000)。随后有M行,每行有两个不相同的整数A、B表示在星球A与B之间存在一条航线。接下来每行有三个整数C、A、B。C为1表示询问当前星球A和星球B之间有多少条关键航线;C为0表示在星球A和星球B之间的航线被破坏,当后面再遇到C为1的情况时,表示询问航线被破坏后,关键路径的情况,且航线破坏后不可恢复; C为-1表示输入文件结束,这时该行没有A,B的值。被破坏的航线数目与询问的次数总和不超过40000。

Output

对每个C为1的询问,输出一行一个整数表示关键航线数目。 注意:我们保证无论航线如何被破坏,任意时刻任意两个星球都能够相互到达。在整个数据中,任意两个星球之间最多只可能存在一条直接的航线。

Sample Input

5 5
1 2
1 3
3 4
4 5
4 2
1 1 5
0 4 2
1 5 1
-1

Sample Output

1
3

end_of_题面

假设我们已经完全清楚了LCT和边双联通分量的基本性质和作用

看到删边先想两个东西——并查集和LCT,并查集虽然不支持删边,但是离线把操作反过来就变成了加边,LCT就不解释了。

然后一看要维护链上信息,并查集就很难搞了,基本确定要用LCT。

发现即使是LCT,题目要维护的信息在删边的情况下也不是很好做,所以我们把操作反过来,这样删边变成加边,查询还是查询。现在问题变成了加一条边对答案有什么影响。接下来上我的灵魂绘图

这里写图片描述

我们发现如果我们有一棵树,我们在两点之间连一条边,那么这两点之间的点和边会组成一个边双联通分量,这些边都会变得不符合题目中所说关键航线的性质,我们可以直接把它们缩成一个点。这样的话,我们在查询的时候只需要查询链有多长就可以了,这个东西LCT可以随便维护。

题目基本解决,细节不清楚的可以看代码

start_of_code

#include <map>
#include <cmath>
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;

const int N = 30000 + 3000;
const int O = 40000 + 4000;
const int M = 100000 + 1000;

map< pair<int, int>, int > p;
pair<int, int> us;
int n, m, ans[O];
struct op{
	int fc, x, y;
}o[O];
struct edge{
	int x, y;
}e[M];

inline int read()
{
    int a = 0;
    char ch;
    int f = 1;
    while(!((((ch = getchar()) >= '0') && (ch <= '9')) || (ch == '-')));
    if(ch == '-')
        f = -1;
    else
    {
        a = a * 10;
        a += ch - '0';
    }
    while((((ch = getchar()) >= '0') && (ch <= '9')) || (ch == '-'))
    {
        a = a * 10;
        a += ch - '0';
    }
    return a * f;
}

namespace LCT{
	int color[N], son[N][2], fa[N], rev[N], sum[N], father[N];
	
	inline void init()
	{
		for(int i = 1;i <= n;++i)
			father[i] = i, color[i] = i;
	}
	
	inline int find(int x)
	{
		if(father[x] != x)
			return father[x] = find(father[x]);
		return x;
	}
	
	inline int findc(int x)
	{
		if(color[x] != x)
			return color[x] = findc(color[x]);
		return x;
	}
	
	inline int which(int x)
	{
		return x == son[fa[x]][1];
	}
	
	inline bool isroot(int x)
	{
		return x != son[fa[x]][0] && x != son[fa[x]][1];
	}
	
	inline void pushdown(int x)
	{
		if(rev[x])
		{
			rev[son[x][0]] ^= 1;
			rev[son[x][1]] ^= 1;
			swap(son[x][0], son[x][1]);
			rev[x] = 0;
		}
	}
	
	inline void update(int x)
	{
		sum[x] = sum[son[x][0]] + sum[son[x][1]] + 1;
	}
	
	inline void rotate(int x)
	{
		int f = fa[x], g = fa[f], d = which(x);
		if(!isroot(f))
			son[g][which(f)] = x;
		son[f][d] = son[x][d ^ 1];
		fa[son[f][d]] = f;
		son[x][d ^ 1] = f;
		fa[f] = x;
		fa[x] = g;
		update(f);
		update(x);
	}
	
	int stk[N], top = 0;
	
	inline void splay(int x)
	{
		top = 0;
		stk[++top] = x;
		int fc;
		for(int i = x;!isroot(i);i = findc(fa[i]))
			stk[++top] = findc(fa[i]);
		for(int i = top;i >= 1;--i)
			pushdown(stk[i]), fa[stk[i]] = findc(fa[stk[i]]);
		for(;!isroot(x);rotate(x))
			if(!isroot(fa[x]))
				rotate(which(x) == which(fa[x]) ? fa[x] : x);
	}
	
	inline void access(int x)
	{
		int y = 0;
		for(;x;y = x, x = findc(fa[x]))
			splay(x), son[x][1] = y, update(x);
	}
	
	inline void makeroot(int x)
	{
		access(x);
		splay(x);
		rev[x] ^= 1;
	}
	
	inline void link(int x, int y)
	{
		makeroot(x);
		fa[x] = y;
		splay(x);
	}
	
	inline void split(int x, int y)
	{
		makeroot(x);
		access(y);
		splay(y);
	}
	
	inline void Uni(int x, int y)
	{
		color[findc(x)] = findc(y);
		pushdown(x);
		if(son[x][0])
			Uni(son[x][0], y);
		if(son[x][1])
			Uni(son[x][1], y);
		son[x][0] = son[x][1] = 0;
	}
}
using namespace LCT;

int main()
{
	n = read(), m = read();
	for(int i = 1;i <= m;++i)
		e[i].x = read(), e[i].y = read();
	init();
	int tot = 1;
	while(true)
	{
		o[tot].fc = read();
		if(o[tot].fc == -1)
		{
			--tot;
			break;
		}
		o[tot].x = read(), o[tot].y = read();
		if(o[tot].fc == 0)
		{
			us.first = o[tot].x;
			us.second = o[tot].y;
			p[us] = 1;
		}
		++tot;
	}
	for(int i = 1;i <= m;++i)
	{
		if(p[make_pair(e[i].x, e[i].y)] != 1)
		{
			int fx = findc(e[i].x), fy = findc(e[i].y);
			if(fx == fy)
				continue;
			int fa = find(fx), fb = find(fy);
			if(fa != fb)
			{
				father[fb] = fa;
				link(fx, fy);
			}
			else
			{
				split(fx, fy);
				Uni(fy, fy);
				update(fy);
			}
		}
	}
	for(int i = tot;i >= 1;--i)
	{
		if(o[i].fc == 0)
		{
			int fx = findc(o[i].x), fy = findc(o[i].y);
			if(fx == fy)
				continue;
			int fa = find(fx), fb = find(fy);
			if(fa != fb)
			{
				father[fb] = fa;
				link(fx, fy);
			}
			else
			{
				split(fx, fy);
				Uni(fy, fy);
				update(fy);
			}
		}
		else
		{
			int fx = findc(o[i].x), fy = findc(o[i].y);
			split(fx, fy);
			ans[i] = sum[fy] - 1;
		}
	}
	for(int i = 1;i <= tot;++i)
		if(o[i].fc == 1)
			printf("%d\n", ans[i]);
	return 0;
}

end_of_code

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值