[BZOJ2959]长跑——新技能:LCT+缩圈

时间:2020-12-05 18:08:58

[BZOJ2959]长跑

试题描述

  某校开展了同学们喜闻乐见的阳光长跑活动。为了能“为祖国健康工作五十年”,同学们纷纷离开寝室,离开教室,离开实验室,到操场参加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。

输入示例


输出示例

-
-

数据规模及约定

对于100%的数据,m<=5n,任意时刻,每个地点的刷卡机台数不超过10000。N<=1.5×105

题解

注意这道题目有双连通分量时是可以设计一种方案跑遍整个双连通分量的,因此简单的LCT维护时不行的,要用到缩圈。

所谓的缩圈即出现一个圈就把它缩成一个点,这个新点的权值等于该圈上所有原来节点权值之和。遍历一遍就可以实现了。

令a = 圈中节点个数,每次缩圈的复杂度为O(a),于此同时整个图的节点个数减少了(a-1)个点,所以总缩圈时间复杂度是O(n)的,那么总时间复杂度为O(n + mlogn)。

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <stack>
#include <vector>
#include <queue>
#include <cstring>
#include <string>
#include <map>
#include <set>
using namespace std; const int BufferSize = 1 << 16;
char buffer[BufferSize], *Head, *tail;
inline char Getchar() {
if(Head == tail) {
int l = fread(buffer, 1, BufferSize, stdin);
tail = (Head = buffer) + l;
}
return *Head++;
}
int read() {
int x = 0, f = 1; char c = Getchar();
while(!isdigit(c)){ if(c == '-') f = -1; c = Getchar(); }
while(isdigit(c)){ x = x * 10 + c - '0'; c = Getchar(); }
return x * f;
} #define maxn 150010
#define maxm 750010
#define oo 2147483647
int n, q; int pa[maxn], pc[maxn];
int findset(int x) { return x == pa[x] ? x : pa[x] = findset(pa[x]); }
int findc(int x) { return x == pc[x] ? x : pc[x] = findc(pc[x]); } int fa[maxn], ch[maxn][2], sumv[maxn], val[maxn], A[maxn];
bool rev[maxn];
bool isroot(int u) { return ch[findc(fa[u])][0] != u && ch[findc(fa[u])][1] != u; }
void maintain(int u) {
int lc = ch[u][0], rc = ch[u][1];
sumv[u] = sumv[lc] + val[u] + sumv[rc];
return ;
}
void pushdown(int u) {
int &lc = ch[u][0], &rc = ch[u][1];
if(rev[u]) {
rev[u] = 0; swap(lc, rc);
rev[lc] ^= 1; rev[rc] ^= 1;
}
return ;
}
void rotate(int u) {
int y = findc(fa[u]), z = findc(fa[y]), l = 0, r = 1;
if(ch[y][1] == u) swap(l, r);
if(!isroot(y)) ch[z][ch[z][1]==y] = u;
fa[u] = z; fa[y] = u; fa[ch[u][r]] = y;
ch[y][l] = ch[u][r]; ch[u][r] = y;
maintain(y); maintain(u);
return ;
}
int S[maxn], top;
void splay(int u) {
bool flag = 0;
S[++top] = u;
for(int t = u; !isroot(t); t = findc(fa[t])) S[++top] = findc(fa[t]);
while(top) pushdown(S[top--]);
if(flag) putchar('\n');
while(!isroot(u)) {
int y = findc(fa[u]), z = findc(fa[y]);
if(!isroot(y)) {
if((ch[y][0] == u) ^ (ch[z][0] == y)) rotate(u);
else rotate(y);
}
rotate(u);
}
return ;
}
void access(int u) {
for(int t = 0; u; u = fa[u]) {
u = findc(u);
splay(u); ch[u][1] = t; maintain(u); t = u;
}
return ;
}
void makeroot(int u) {
access(u); splay(u); rev[u] ^= 1;
return ;
}
void link(int a, int b) {
makeroot(a); fa[a] = b;
return ;
}
void split(int a, int b) {
makeroot(b); access(a); splay(a);
return ;
} void dfs(int u, int v) {
if(!u) return ;
pushdown(u);
pc[u] = v;
if(u != v) val[v] += val[u];
dfs(ch[u][0], v); dfs(ch[u][1], v);
ch[u][0] = ch[u][1] = 0;
return ;
} int main() {
n = read(); q = read();
for(int i = 1; i <= n; i++) A[i] = val[i] = read(), pa[i] = pc[i] = i;
while(q--) {
int p = read(), a = read(), b = read();
if(p == 1) {
a = findc(a); b = findc(b); if(a == b) continue;
int u = findset(a), v = findset(b);
if(u != v) pa[v] = u, link(a, b);
else {
split(a, b); dfs(a, a); maintain(a);
}
}
if(p == 2) {
int c = findc(a);
splay(c);
val[c] += b - A[a]; A[a] = b;
maintain(c);
}
if(p == 3) {
a = findc(a); b = findc(b);
int u = findset(a), v = findset(b);
if(u != v) puts("-1");
else split(a, b), printf("%d\n", sumv[a]);
}
} return 0;
}