题目描述
给出一张有向图,每个点最多只有一条出边。多次修改一个点的出边,并询问从某个点开始经过多少个点能够到达出度为0的点(走不到则输出-1)
输入
第一行为两个整数N和M,代表序列长度和操作次数。
第二行为N个整数,代表初始的小伙伴序列。
接下来有M行,每行代表一个操作。
如果这一行的第一个数是1,代表该操作是一个询问操作,接下来一个数X,代表询问此时大爷从X处,经过几次弹
起会摔在地上。如果永远不会摔在地上,请输出-1。
如果这一行的第一个数是2,代表该操作是一个更改操作,接下来两个数X,Y,代表将序列的第X项改为Y。
N,M <= 200000 |Ai| < N
输出
对于每次询问操作,输出弹起次数或-1。
样例输入
3 19
1 1 1
1 1
1 2
1 3
2 1 2
1 1
1 2
1 3
2 3 -1
1 1
1 2
1 3
2 2 233
1 1
1 2
1 3
2 2 -233
1 1
1 2
1 3
样例输出
3
2
1
2
2
1
-1
-1
-1
3
1
2
3
1
2
题解
LCT
容易发现每个“连通块”都是有根树或基环内向树。
对于有根树可以直接使用LCT维护。
对于基环内向树可以看作是有根树加上一条从根节点到某个点的边。我们使用LCT维护这个有根树,并在根节点出记录一下这条边到的点是哪个。
对于link操作如果是普通边则直接连,如果是这一条特殊的边则记录下来。
对于cut操作如果割断的是特殊的边则直接更改记录的信息,否则割断以后还要判断这条边加上后是否形成环,如果不形成环的话还要把这个记录的边当做一般的树边link上。
另外需要注意的是:有根树不能makeroot,但是仍然需要access&splay完成操作。
具体细节还是看代码吧。
时间复杂度 $O(LCT·nlog n)$
本题TLE不一定是因为常数大,很有可能是写挂了。
#include <cstdio> #include <cstring> #include <algorithm> #define N 200010 using namespace std; int a[N] , fa[N] , c[2][N] , si[N] , pos[N]; inline void pushup(int x) { si[x] = si[c[0][x]] + si[c[1][x]] + 1; } inline bool isroot(int x) { return x != c[0][fa[x]] && x != c[1][fa[x]]; } inline void rotate(int x) { int y = fa[x] , z = fa[y] , l = (c[1][y] == x) , r = l ^ 1; if(!isroot(y)) c[c[1][z] == y][z] = x; fa[x] = z , fa[y] = x , fa[c[r][x]] = y , c[l][y] = c[r][x] , c[r][x] = y; pushup(y) , pushup(x); } inline void splay(int x) { int y , z; while(!isroot(x)) { y = fa[x] , z = fa[y]; if(!isroot(y)) { if((c[0][y] == x) ^ (c[0][z] == y)) rotate(x); else rotate(y); } rotate(x); } } inline void access(int x) { int t = 0; while(x) splay(x) , c[1][x] = t , pushup(x) , t = x , x = fa[x]; } inline int find(int x) { access(x) , splay(x); while(c[0][x]) x = c[0][x]; splay(x); return x; } inline void link(int x , int y) { if(find(y) == x) pos[x] = y; else access(x) , splay(x) , fa[x] = y; } inline void cut(int x , int y) { if(pos[x] == y) pos[x] = 0; else { int t = find(x); access(x) , splay(x) , fa[c[0][x]] = 0 , c[0][x] = 0 , pushup(x); if(pos[t] && find(pos[t]) != t) link(t , pos[t]) , pos[t] = 0; } } int main() { int n , m , i , opt , x , y; scanf("%d%d" , &n , &m); for(i = 1 ; i <= n ; i ++ ) scanf("%d" , &a[i]) , si[i] = 1; for(i = 1 ; i <= n ; i ++ ) if(i + a[i] >= 1 && i + a[i] <= n) link(i , i + a[i]); while(m -- ) { scanf("%d%d" , &opt , &x); if(opt == 1) { access(x) , y = find(x); if(pos[y]) puts("-1"); else splay(x) , printf("%d " , si[x]); } else { scanf("%d" , &y); if(x + a[x] >= 1 && x + a[x] <= n) cut(x , x + a[x]); if(x + y >= 1 && x + y <= n) link(x , x + y); a[x] = y; } } return 0; }