BZOJ2959长跑——LCT+并查集(LCT动态维护边双连通分量)
题目描述
某校开展了同学们喜闻乐见的阳光长跑活动。为了能“为祖国健康工作五十年”,同学们纷纷离开寝室,离开教室,离开实验室,到操场参加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。
样例输入
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
80
170
180
170
190
170
250
280
280
270
370
380
580
提示
数据规模及约定
对于100%的数据,m<=5n,任意时刻,每个地点的刷卡机台数不超过10000。N<=1.5×105
可以发现对于每一个边双只要能走进这个边双那么边双里面所有点就都能到达。
将每个边双缩成一个点,用LCT来维护缩点后的树。
因此连边可以分成两种情况讨论:
1、当两点联通但不属于同一个边双时,那么加入这条边之后这两点及两点之前路径上的点组成了一个新的边双,在LCT上dfs这些路径上的点将他们的大小合并到新形成的边双代表的点上并将这些点删除。
2、当两点不连通时,这时连接两点不会出现新的边双,直接在LCT上连接两点即可。
用两个并查集,分别维护每个点属于哪个边双及点与点之间的连通性。注意点之间连通性不能用LCT的find找(会TLE),而要用并查集。
因为每个点和边最多只会被删除一次也就是只会被dfs一次,所以时间复杂度是O((n+m)*logn)
#include<set> #include<map> #include<queue> #include<stack> #include<cmath> #include<vector> #include<bitset> #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #define pr pair<int,int> #define ll long long using namespace std; int g[150010]; int fa[150010]; int f[150010]; int s[150010][2]; int v[150010]; int sum[150010]; int size[150010]; int st[150010]; int r[150010]; int n,m; int opt; int x,y; int find(int x) { if(fa[x]==x) { return x; } return fa[x]=find(fa[x]); } int judge(int x) { if(g[x]==x) { return x; } return g[x]=judge(g[x]); } int is_root(int rt) { return rt!=s[find(f[rt])][0]&&rt!=s[find(f[rt])][1]; } int get(int rt) { return rt==s[find(f[rt])][1]; } void pushup(int rt) { sum[rt]=sum[s[rt][0]]+sum[s[rt][1]]+size[rt]; } void pushdown(int rt) { if(r[rt]) { swap(s[rt][0],s[rt][1]); r[s[rt][0]]^=1; r[s[rt][1]]^=1; r[rt]^=1; } } void rotate(int rt) { int fa=find(f[rt]); int anc=find(f[fa]); int k=get(rt); if(!is_root(fa)) { s[anc][get(fa)]=rt; } s[fa][k]=s[rt][k^1]; f[s[fa][k]]=fa; s[rt][k^1]=fa; f[fa]=rt; f[rt]=anc; pushup(fa); pushup(rt); } void splay(int rt) { int top=0; st[++top]=rt; for(int i=rt;!is_root(i);i=find(f[i])) { st[++top]=find(f[i]); } for(int i=top;i>=1;i--) { pushdown(st[i]); } for(int fa;!is_root(rt);rotate(rt)) { if(!is_root(fa=find(f[rt]))) { rotate(get(fa)==get(rt)?fa:rt); } } } void access(int rt) { for(int x=0;rt;x=rt,rt=find(f[rt])) { splay(rt); s[rt][1]=x; pushup(rt); } } void reverse(int rt) { access(rt); splay(rt); r[rt]^=1; } void link(int x,int y) { reverse(x); f[x]=y; } void dfs(int x,int rt) { fa[x]=rt; if(s[x][0]) { dfs(s[x][0],rt); } if(s[x][1]) { dfs(s[x][1],rt); } } int main() { scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) { scanf("%d",&v[i]); size[i]=v[i]; sum[i]=v[i]; fa[i]=i; g[i]=i; } while(m--) { scanf("%d%d%d",&opt,&x,&y); int fx=find(x); int fy=find(y); if(opt==1) { if(fx!=fy) { if(judge(fx)!=judge(fy)) { link(fx,fy); g[g[fx]]=g[fy]; } else { reverse(fx); access(fy); splay(fy); size[fy]=sum[fy]; dfs(fy,fy); s[fy][0]=0; } } } else if(opt==2) { splay(fx); size[fx]+=y-v[x]; sum[fx]+=y-v[x]; v[x]=y; } else { if(judge(fx)!=judge(fy)) { printf("-1\n"); } else { reverse(fx); access(fy); splay(fy); printf("%d\n",sum[fy]); } } } }