bzoj1180 [CROATIAN2009]OTOCI(详解LCT操作)

Description

给出n个结点以及每个点初始时对应的权值wi。起始时点与点之间没有连边。有3类操作: 1、bridge A B:询问结点A与结点B是否连通。如果是则输出“no”。否则输出“yes”,并且在结点A和结点B之间连一条无向边。 2、penguins A X:将结点A对应的权值wA修改为X。 3、excursion A B:如果结点A和结点B不连通,则输出“impossible”。否则输出结点A到结点B的路径上的点对应的权值的和。给出q个操作,要求在线处理所有操作。数据范围:1<=n<=30000, 1<=q<=300000, 0<=wi<=1000。

Input

第一行包含一个整数n(1<=n<=30000),表示节点的数目。第二行包含n个整数,第i个整数表示第i个节点初始时对应的权值。第三行包含一个整数q(1<=n<=300000),表示操作的数目。以下q行,每行包含一个操作,操作的类别见题目描述。任意时刻每个节点对应的权值都是1到1000的整数。

Output

输出所有bridge操作和excursion操作对应的输出,每个一行。

Sample Input

5
4 2 4 5 6
10
excursion 1 1
excursion 1 2
bridge 1 2
excursion 1 2
bridge 3 4
bridge 3 5
excursion 4 5
bridge 1 3
excursion 2 4
excursion 2 5

Sample Output

4
impossible
yes
6
yes
yes
15
yes
15
16


分析: 发现一个讲得很好的[blog](http://blog.csdn.net/JeremyGJY/article/details/51078087) 这篇blog使我对LCT的各个操作有了更清晰的理解 我们只要记住:

expose是访问操作,可以把x与根节点的路径变成偏爱路径
makeroot是换根操作,可以把树形结构的根换成x

其余的所有操作都是建立在这两个操作上的

Cut:

我们需要断开(x,y)
同理,我们先把x变成整棵树的根节点
之后我们expose(y),splay(y),
因为(x,y)之前是相连的,所以这样操作之后,x和y一定在一个辅助树中
x是根节点,ta的深度最小,y是辅助树的根节点
所以x一定是y的左儿子
pre[x]=0,ch[y][0]=0

我们要连接(x,y)
既然我们能够cut,那我们就可以link
于是我们可以先把整棵树的根节点变成x:(makeroot(x)),这样x就没有pre
所以我们只要简单的把pre[x]变成y即可

Find:

这个操作可以把找到当前树结构中的根节点
我们只要访问一下x,把ta转换到辅助树的根上
之后一路向左找就可以了(根节点一定是深度最浅的一个点)

两点连通性:

find(x)==find(y)
显而易见

路径权值和:

LCT的优点就是ta的形态不固定
我们可以把路径中的一个端点视为根节点(比如说x):makeroot(x)
之后只要expose(y),这样x到y的路径就变成了偏爱路径,
splay(y),y节点上的sum就是路径权值和啦

节点到根的距离:

首先我们要把给定的根换到根的位置上:makeroot(root)
之后就像询问路径权值和一样
expose(x),splay(x),
返回值是size[ch[x][0]]

更改节点值:

我们要改变一个结点的权值,
我们当然希望涉及到的结点尽量少,那么什么样的结点改变ta的值影响的结点维护值最少呢,当然是根节点
所以我们首先makeroot(x)
直接修改x的值就可以了
最后不要忘了update(x)

tip

rotate,splay,expose,makeroot这四个过程是最重要的
一定不要写错了

#include<cstdio>
#include<cstring>
#include<iostream>

using namespace std;

const int N=300010;
int pre[N],ch[N][2],v[N],sum[N],q[N];
bool rev[N];
int n,m;

int get(int bh)
{
    return (ch[pre[bh]][0]==bh ? 0:1);
}

int isroot(int bh)
{
    return ch[pre[bh]][0]!=bh&&ch[pre[bh]][1]!=bh;
}

void update(int bh)
{
    if (!bh) return;
    sum[bh]=v[bh];
    if (ch[bh][0]) sum[bh]+=sum[ch[bh][0]];
    if (ch[bh][1]) sum[bh]+=sum[ch[bh][1]];
}

void push(int bh)
{
    int lc=ch[bh][0];
    int rc=ch[bh][1];
    if (bh&&rev[bh])
    {
        if (lc) rev[lc]^=1;
        if (rc) rev[rc]^=1;
        rev[bh]^=1;
        swap(ch[bh][0],ch[bh][1]);
    }
}

void rotate(int bh)
{
    int fa=pre[bh];
    int grand=pre[fa];
    int wh=get(bh);
    if (!isroot(fa)) ch[grand][ch[grand][0]==fa ? 0:1]=bh;
    pre[bh]=grand;
    ch[fa][wh]=ch[bh][wh^1];
    pre[ch[fa][wh]]=fa;
    ch[bh][wh^1]=fa;
    pre[fa]=bh;
    update(fa);
    update(bh);
}

void splay(int bh)
{
    int top=0;
    q[++top]=bh;
    for (int i=bh;!isroot(i);i=pre[i])      //!isroot(i)
        q[++top]=pre[i];
    while (top) push(q[top--]);

    for (int fa;!isroot(bh);rotate(bh))
        if (!isroot(fa=pre[bh]))
            rotate(get(fa)==get(bh)? fa:bh);
}

void expose(int bh)
{
    int t=0;
    while (bh)
    {
        splay(bh);
        ch[bh][1]=t;
        update(bh);      //产生了一个新儿子,所以需要update 
        t=bh;
        bh=pre[bh];
    }
}

void makeroot(int bh)    //把bh变成树的根 
{
    expose(bh);
    splay(bh);
    rev[bh]^=1;
}

void link(int x,int y)
{
    makeroot(x);         //这样x就没有pre了 
    pre[x]=y;     
}

void cut(int x,int y)
{
    makeroot(x);
    expose(y); splay(y); //这样x一定在y的左儿子中 
    pre[x]=ch[y][0]=0;
    update(x);
}

int find(int bh)
{
    expose(bh);          //访问一下,使得根到bh的路径变成偏爱路径 
    splay(bh);
    while (ch[bh][0]) bh=ch[bh][0];
    return bh;
}

int main()
{
    char s[30];
    scanf("%d",&n);
    for (int i=1;i<=n;i++) scanf("%d",&v[i]),sum[i]=v[i];
    scanf("%d",&m);
    for (int i=1;i<=m;i++)
    {
        scanf("%s",s);
        int x,y;
        scanf("%d%d",&x,&y);
        if (s[0]=='e')          //路径权值和 
        {
            if (find(x)!=find(y)) printf("impossible\n");
            else
            {
                makeroot(x);
                expose(y);
                splay(y);
                printf("%d\n",sum[y]);
            }
        }
        else if (s[0]=='b')
        {
            if (find(x)!=find(y))
                printf("yes\n"),link(x,y);
            else printf("no\n");
        }
        else
        {
            makeroot(x);       //改变单个结点值 
            splay(x);
            v[x]=y;
            update(x);
        }
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值