zoukankan      html  css  js  c++  java
  • 【bzoj4764】弹飞大爷 LCT

    题目描述

    给出一张有向图,每个点最多只有一条出边。多次修改一个点的出边,并询问从某个点开始经过多少个点能够到达出度为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;
    }
    
  • 相关阅读:
    Git tag
    Docker学习笔记五 仓库
    Docker学习笔记四 Docker容器
    Docker学习笔记二 使用镜像
    Docker学习笔记一 概念、安装、镜像加速
    element-UI 下拉条数多渲染慢
    scroll-view——小程序横向滚动
    Jquery slider范围滑块,为两个滑块设置不同的setp值
    自说自话2
    自说自话1
  • 原文地址:https://www.cnblogs.com/GXZlegend/p/7883052.html
Copyright © 2011-2022 走看看