长跑

内存限制:128 MiB

时间限制:1000 ms

【题目描述】

某校开展了同学们喜闻乐见的阳光长跑活动。为了能“为祖国健康工作五十 年”,同学们纷纷离开寝室,离开教室,离开实验室,到操场参加 3000 米长跑运 动。一时间操场上熙熙攘攘,摩肩接踵,盛况空前。 为了让同学们更好地监督自己,学校推行了刷卡机制。 学校中有 n 个地点,用 1 到 n 的整数表示,每个地点设有若干个刷卡机。 有以下三类事件: 1、修建了一条连接 A 地点和 B 地点的跑道。 2、A 点的刷卡机台数变为了 B。 3、进行了一次长跑。问一个同学从 A 出发,最后到达 B 最多可以刷卡多少 次。具体的要求如下: 当同学到达一个地点时,他可以在这里的每一台刷卡机上都刷卡。但每台刷 卡机只能刷卡一次,即使多次到达同一地点也不能多次刷卡。 为了安全起见,每条跑道都需要设定一个方向,这条跑道只能按照这个方向 单向通行。最多的刷卡次数即为在任意设定跑道方向,按照任意路径从 A 地点 到 B 地点能刷卡的最多次数。

【输入描述】

输入的第一行包含两个正整数 n,m,表示地点的个数和操作的个数。 第二行包含 n 个非负整数,其中第 i 个数为第个地点最开始刷卡机的台数。 接下来有 m 行,每行包含三个非负整数 P,A,B,P 为事件类型,A,B 为事件 的两个参数。 最初所有地点之间都没有跑道。 每行相邻的两个数之间均用一个空格隔开。表示地点编号的数均在 1 到 n 之间,每个地点的刷卡机台数始终不超过 10000,P=1,2,3。

【输出描述】

输出的行数等于第 3 类事件的个数,每行表示一个第 3 类事件。如果该情况 下存在一种设定跑道方向的方案和路径的方案,可以到达,则输出最多可以刷卡 的次数。如果 A 不能到达 B,则输出-1。

输入样例:

9 31
10 20 30 40 50 60 70 80 90
3 1 2
1 1 3
1 1 2
1 8 9
1 2 4
1 2 5
1 4 6
1 4 7
3 1 8
3 8 8
1 8 9
3 8 8
3 7 5
3 7 3
1 4 1
3 7 5
3 7 3
1 5 7
3 6 5
3 3 6
1 2 4
1 5 5
3 3 6
2 8 180
3 8 8
2 9 190
3 9 9
2 5 150
3 3 6
2 1 210
3 3 6

输出样例:

-1
-1
80
170
180
170
190
170
250
280
280
270
370
380
580

 

 

 

题解

感觉好LCT啊。。。

先考虑一个简单的问题:

如何在一个支持加边的图中维护两点的连通性?

这不无脑并查集吗。。。

再考虑一个‘简单’的问题:

如何在一个支持加边的图中维护两点是否在同一个边双连通分量?

似乎不会。。。

发现形成的第一个边双连通分量一定是一个环

想了一下,发现可以用LCT+两个并查集

先用一个并查集check来维护连通性

再用一个并查集real来维护边双连通分量的集合

当我们加上一条边(u,v)时:

如果u,v不连通,就把它们在check上合并

如果u,v连通但不在一个边双连通分量,就把它们在LCT上的路径spilt出来,把路径上的点一个一个在real上合并

感觉会T,于是我们缩一下点,把已经合并了real的点从LCT中删掉,只保留它们的祖宗,祖宗要统计它们的所有信息

如果u,v在同一个边双连通分量里,就不管了

 

让我们回到这个题,我们任务的就是求两点路径的点权和+两点路径上的边双连通分量总点权和

我们就可以用上面的思路来维护边双连通分量的点权和

于是我们维护的就是一个由边双连通分量祖先构成的LCT

如图:

如果在已删除节点之间连边,就会使两个边双祖先合并,就会删掉其中之一

所以不同祖先的已删除节点都无连边。

代码实现还有许多细节:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define N 100005
int n,m,val[N],a[N];//a是实际点权,val是祖先点权
struct node{
	int f[N];
	void init(){for(int i=1;i<=n;++i)f[i]=i;}
	int find(int x){return x==f[x]?x:f[x]=find(f[x]);}
	void uni(int x,int y){f[find(x)]=find(y);}
}real,check;
int fa[N],ch[N][2],sum[N];
bool rev[N];
inline bool pdc(int x){return ch[fa[x]][1]==x;}
inline bool pdr(int x){return ch[fa[x]][0]!=x&&ch[fa[x]][1]!=x;}
inline void pushdown(int x)
{
	if(rev[x]){
		swap(ch[x][0],ch[x][1]);
		if(ch[x][0])rev[ch[x][0]]^=1;
		if(ch[x][1])rev[ch[x][1]]^=1;
		rev[x]=0;
	}
}
void update(int x){sum[x]=sum[ch[x][0]]+sum[ch[x][1]]+val[x];}
inline void rot(int x)
{
	int y=fa[x],z=fa[y];
	bool flg=pdc(x);
	if(!pdr(y)) ch[z][pdc(y)]=x;
	if(ch[y][flg]=ch[x][flg^1])
		fa[ch[y][flg]]=y;
	ch[x][flg^1]=y;
	fa[y]=x;fa[x]=z;
	update(y);update(x);
}
void pdpath(int x){if(!pdr(x)) pdpath(fa[x]);pushdown(x);}
inline void splay(int x)
{
	for(pdpath(x);!pdr(x);rot(x))
		if(!pdr(fa[x]))
			rot(pdc(fa[x])==pdc(x)?fa[x]:x);
}
inline void Access(int x)
{
    for(int last=0;x;last=x,x=fa[x]=real.find(fa[x])){//边Access边路径压缩找祖先,因为有可能遇到已删除的节点
		splay(x);
		ch[x][1]=last;
		update(x);
	}
}
inline void beroot(int x){Access(x);splay(x);rev[x]^=1;}
inline void link(int x,int y){beroot(x);fa[x]=y;}
void dfs(int &u,int d)//dfs只找LCT上的节点(即未删除节点)
{
	if(!u) return;
	real.uni(u,d);
	val[d]+=val[u];//把所有点权都统计加边双祖先上
	dfs(ch[u][0],d);
	dfs(ch[u][1],d);
	u=0;//删掉节点
}
int main()
{
	int i,op,u,v,d;
	scanf("%d%d",&n,&m);
	for(i=1;i<=n;i++)
		scanf("%d",&a[i]);
	for(i=1;i<=n;i++)
		val[i]=a[i];
	real.init();check.init();
	for(i=1;i<=m;i++){
		scanf("%d%d%d",&op,&u,&v);
		if(op==1){
			u=real.find(u);v=real.find(v);
			if(u==v) continue;
			if(check.find(u)!=check.find(v)){link(u,v);check.uni(u,v);}
			else{
				beroot(u);Access(v);splay(v);//把路径spilt出来
				dfs(ch[v][0],v);update(v);
			}
		}
		else if(op==2){
			d=real.find(u);splay(d);
			val[d]+=v-a[u];a[u]=v;
			update(d);
		}
		else{
			if(check.find(u)!=check.find(v)){
				printf("-1\n");
				continue;
			}
			u=real.find(u);v=real.find(v);
			beroot(u);Access(v);splay(v);
			printf("%d\n",sum[ch[v][0]]+val[v]);
		}
	}
}

 

 

好久都没写LCT了。。。