• Link Cut Tree学习笔记


    最近学习了LCT,这种基于Splay的数据结构真是给人一种优美的感觉啊。


    简介

    Link Cut Tree是一种支持动态加边删边以维护动态森林的数据结构,其核心思想是将一棵树分为若干棵Splay(辅助树),通过对Splay进行操作去维护原树(路径);

    类比于树链剖分中将树剖分为重链,LCT也是将树分为若干条链,其中每一条链都是一棵以节点实际深度为排序关键字的SplaySplay的根节点指向另一棵Splay

    在一棵Splay(重链)中的边被为重边(偏爱边),连接两棵Splay的边为轻边,如下图,红边为重边:


    基本操作

    1、$ Access(x) $:访问节点$x$,其含义是打通一条从根到$x$的路径,即将这条路径上所有的边变为重边,将所有与这条路径相连的边变为轻边;

    实现:不断将$x$旋转至Splay的根,然后断掉它原来的右儿子相连的边,改为连接$las$(上一个处理的$x$),然后处理$x$的父亲,直至到LCT的树根;

    LCT的大部分操作基于$Access$,而$Access$的复杂度是均摊$logn$的,并不会证明orz,可以去看一下杨哲dalao的集训队论文

    1 void access(int x)
    2 {
    3     for(int las=0;x;las=x,x=tree[x].fa)
    4     {
    5         splay(x),tree[x].son[1]=las;
    6         pull_up(x);
    7     }
    8 }
    Access

    2、$ Make$_$Root(x) $:将$x$作为它所在LCT的树根;

    实现:访问$x$,将$x$旋转到根,然后翻转$x$所在的Splay

    关于翻转,因为这里的Splay按照节点实际深度排序,而换根改变了排序键值,因此翻转一下,把链倒过来即可;

    1 void make_root(int x)
    2 {
    3     access(x),splay(x),reverse(x);
    4 }
    Make_Root

    3、$ Link(i,j) $:连接$i$,$j$;

    实现:将$i$作为树根,然后把$i$父亲赋值为$j$;

    1 void link(int i,int j)
    2 {
    3     make_root(i),tree[i].fa=j;
    4 }
    Link

    4、$ Cut(i,j) $:断开连接$i$,$j$的边;

    实现:将$i$作为树根,访问$j$,将$j$旋转到根,断开$j$与其左儿子;

    $i$与$j$在原树中一定直接相连,辅助树中也是如此,因此访问$j$并旋转后$i$一定是$j$的左儿子;

    1 void cut(int i,int j)
    2 {
    3     make_root(i),access(j),splay(j);
    4     tree[i].fa=tree[j].son[0]=0;
    5     pull_up(j);
    6 }
    Cut

    5、$ Find$_$Root(x) $:查询$x$所在LCT的根;

    实现:访问$x$后找到它所在辅助树的第一个节点即可;

    1 int find_root(int x)
    2 {
    3     access(x),splay(x);
    4     while(tree[x].son[0])
    5     {
    6         x=tree[x].son[0];
    7     }
    8     return x;
    9 }
    Find_Root

    模板(题)

    2594: [Wc2006]水管局长数据加强版

    Time Limit: 25 Sec  Memory Limit: 128 MB
    Submit: 4094  Solved: 1272
    [Submit][Status][Discuss]

    Description

    SC省MY市有着庞大的地下水管网络,嘟嘟是MY市的水管局长(就是管水管的啦),嘟嘟作为水管局长的工作就是:每天供水公司可能要将一定量的水从x处送往y处,嘟嘟需要为供水公司找到一条从A至B的水管的路径,接着通过信息化的控制中心通知路径上的水管进入准备送水状态,等到路径上每一条水管都准备好了,供水公司就可以开始送水了。嘟嘟一次只能处理一项送水任务,等到当前的送水任务完成了,才能处理下一项。
    在处理每项送水任务之前,路径上的水管都要进行一系列的准备操作,如清洗、消毒等等。嘟嘟在控制中心一声令下,这些水管的准备操作同时开始,但由于各条管道的长度、内径不同,进行准备操作需要的时间可能不同。供水公司总是希望嘟嘟能找到这样一条送水路径,路径上的所有管道全都准备就绪所需要的时间尽量短。嘟嘟希望你能帮助他完成这样的一个选择路径的系统,以满足供水公司的要求。另外,由于MY市的水管年代久远,一些水管会不时出现故障导致不能使用,你的程序必须考虑到这一点。
    不妨将MY市的水管网络看作一幅简单无向图(即没有自环或重边):水管是图中的边,水管的连接处为图中的结点。

    Input

    输入文件第一行为3个整数:N, M, Q分别表示管道连接处(结点)的数目、目前水管(无向边)的数目,以及你的程序需要处理的任务数目(包括寻找一条满足要求的路径和接受某条水管坏掉的事实)。
    以下M行,每行3个整数x, y和t,描述一条对应的水管。x和y表示水管两端结点的编号,t表示准备送水所需要的时间。我们不妨为结点从1至N编号,这样所有的x和y都在范围[1, N]内。
    以下Q行,每行描述一项任务。其中第一个整数为k:若k=1则后跟两个整数A和B,表示你需要为供水公司寻找一条满足要求的从A到B的水管路径;若k=2,则后跟两个整数x和y,表示直接连接x和y的水管宣布报废(保证合法,即在此之前直接连接x和y尚未报废的水管一定存在)。

    Output

    按顺序对应输入文件中每一项k=1的任务,你需要输出一个数字和一个回车/换行符。该数字表示:你寻找到的水管路径中所有管道全都完成准备工作所需要的时间(当然要求最短)。
     
     
    最后挂道题吧,LCT维护最小生成树,需要删边;
    离线后变为加边,维护路径最大值即可;
    AC GETDAZE
     
    ↓代码
      1 #include<algorithm>
      2 #include<iostream>
      3 #include<complex>
      4 #include<cstring>
      5 #include<string>
      6 #include<cstdio>
      7 #include<vector>
      8 #include<cmath>
      9 #include<queue>
     10 #include<map>
     11 #include<set>
     12 #define N 100039
     13 #define mod 20070831
     14 #define inf 0x3f3f3f3f
     15 #define ll long long
     16 using namespace std;
     17 struct question
     18 {
     19     int opt,i,j,ans;
     20 }ask[N];
     21 struct edge
     22 {
     23     int u,v,w,des;
     24 }net[N*10];
     25 struct Sinogi
     26 {
     27     int fa,son[2],w,mx,mr,rev;
     28 }tree[N*15];
     29 int n,m,q,fa[N],des[N];
     30 inline int is_root(int x)
     31 {
     32     return tree[tree[x].fa].son[0]!=x && tree[tree[x].fa].son[1]!=x;
     33 }
     34 void reverse(int x)
     35 {
     36     swap(tree[x].son[0],tree[x].son[1]);
     37     tree[x].rev^=1;
     38 }
     39 void pull_up(int x)
     40 {
     41     if(tree[tree[x].son[0]].mx>tree[tree[x].son[1]].mx)
     42     {
     43         tree[x].mx=tree[tree[x].son[0]].mx;
     44         tree[x].mr=tree[tree[x].son[0]].mr;
     45     }
     46     else
     47     {
     48         tree[x].mx=tree[tree[x].son[1]].mx;
     49         tree[x].mr=tree[tree[x].son[1]].mr;
     50     }
     51     if(tree[x].w>tree[x].mx)
     52     {
     53         tree[x].mx=tree[x].w,tree[x].mr=x-n;
     54     }
     55 }
     56 void push_down(int x)
     57 {
     58     if(tree[x].rev)
     59     {
     60         reverse(tree[x].son[0]),reverse(tree[x].son[1]);
     61         tree[x].rev^=1;
     62     }
     63 }
     64 void Push_Down(int x)
     65 {
     66     if(!is_root(x))
     67     {
     68         Push_Down(tree[x].fa);
     69     }
     70     push_down(x);
     71 }
     72 inline int get(int x)
     73 {
     74     return tree[tree[x].fa].son[1]==x;
     75 }
     76 void rotate(int x)
     77 {
     78     int fa=tree[x].fa,wh=get(x);
     79     if(!is_root(fa))
     80     {
     81         tree[tree[fa].fa].son[get(fa)]=x;
     82     }
     83     tree[x].fa=tree[fa].fa;
     84     tree[fa].son[wh]=tree[x].son[wh^1];
     85     tree[tree[fa].son[wh]].fa=fa;
     86     tree[x].son[wh^1]=fa;
     87     tree[fa].fa=x;
     88     pull_up(fa),pull_up(x);
     89 }
     90 void splay(int x)
     91 {
     92     Push_Down(x);
     93     for(int fa=tree[x].fa;!is_root(x);fa=tree[x].fa)
     94     {
     95         if(!is_root(fa))
     96         {
     97             rotate(get(fa)==get(x) ? fa : x);
     98         }
     99         rotate(x);
    100     }
    101     pull_up(x);
    102 }
    103 void access(int x)
    104 {
    105     for(int las=0;x;las=x,x=tree[x].fa)
    106     {
    107         splay(x),tree[x].son[1]=las;
    108         pull_up(x);
    109     }
    110 }
    111 void make_root(int x)
    112 {
    113     access(x),splay(x),reverse(x);
    114 }
    115 void link(int i,int j)
    116 {
    117     make_root(i),tree[i].fa=j;
    118 }
    119 void cut(int i,int j)
    120 {
    121     make_root(i),access(j),splay(j);
    122     tree[i].fa=tree[j].son[0]=0;
    123     pull_up(j);
    124 }
    125 int query_max(int i,int j)
    126 {
    127     make_root(i),access(j),splay(i);
    128     return tree[i].mr;
    129 }
    130 bool cmp_u_v(edge a,edge b)
    131 {
    132     return a.u==b.u ? a.v<b.v : a.u<b.u;
    133 }
    134 bool cmp_w(edge a,edge b)
    135 {
    136     return a.w<b.w;
    137 }
    138 int search(int i,int j)
    139 {
    140     int l,r,L,R,mid,res;
    141     l=1,r=m;
    142     while(l<=r)
    143     {
    144         mid=l+r>>1;
    145         if(net[mid].u>=i) r=mid-1,L=mid;
    146         else l=mid+1;
    147     }
    148     l=1,r=m;
    149     while(l<=r)
    150     {
    151         mid=l+r>>1;
    152         if(net[mid].u<=i) l=mid+1,R=mid;
    153         else r=mid-1;
    154     }
    155     while(L<=R)
    156     {
    157         mid=L+R>>1;
    158         if(net[mid].v>=j) R=mid-1,res=mid;
    159         else L=mid+1;
    160     }
    161     return res;
    162 }
    163 int find(int k)
    164 {
    165     return fa[k]==k ? k : fa[k]=find(fa[k]);
    166 }
    167 void Kruskal()
    168 {
    169     sort(net+1,net+m+1,cmp_w);
    170     for(int a=1;a<=n;a++)
    171     {
    172         fa[a]=a;
    173     }
    174     for(int a=1,b,c;a<=m;a++)
    175     {
    176         tree[n+a].w=net[a].w;
    177         if(net[a].des)
    178         {
    179             des[net[a].des]=a;
    180         }
    181         else
    182         {
    183             b=find(net[a].u),c=find(net[a].v);
    184             if(b!=c)
    185             {
    186                 fa[c]=b;
    187                 link(net[a].u,n+a),link(n+a,net[a].v);
    188             }
    189         }
    190     }
    191 }
    192 int main()
    193 {
    194     int stp;
    195     scanf("%d%d%d",&n,&m,&q);
    196     for(int a=1;a<=m;a++)
    197     {
    198         scanf("%d%d%d",&net[a].u,&net[a].v,&net[a].w);
    199         if(net[a].u>net[a].v)
    200         {
    201             swap(net[a].u,net[a].v);
    202         }
    203     }
    204     sort(net+1,net+m+1,cmp_u_v);
    205     for(int a=1;a<=q;a++)
    206     {
    207         scanf("%d%d%d",&ask[a].opt,&ask[a].i,&ask[a].j);
    208         if(ask[a].opt==2)
    209         {
    210             if(ask[a].i>ask[a].j)
    211             {
    212                 swap(ask[a].i,ask[a].j);
    213             }
    214             net[search(ask[a].i,ask[a].j)].des=a;
    215         }
    216     }
    217     Kruskal();
    218     for(int a=q;a>=1;a--)
    219     {
    220         stp=query_max(ask[a].i,ask[a].j);
    221         if(ask[a].opt==1)
    222         {
    223             ask[a].ans=net[stp].w;
    224         }
    225         else
    226         {
    227             if(net[des[a]].w<net[stp].w)
    228             {
    229                 make_root(net[stp].u),access(net[stp].v),splay(n+stp);
    230                 cut(net[stp].u,n+stp),cut(n+stp,net[stp].v);
    231                 link(ask[a].i,n+des[a]),link(n+des[a],ask[a].j);
    232             }
    233         }
    234     }
    235     for(int a=1;a<=q;a++)
    236     {
    237         if(ask[a].opt==1)
    238         {
    239             printf("%d
    ",ask[a].ans);
    240         }
    241     }
    242     return 0;
    243 }
    bzoj2594
  • 相关阅读:
    二维数组中的查找
    循环语句
    掷骰子游戏和条件语句
    类型转换与键盘输入
    运算符(2)
    运算符(1)
    面向对象(2)
    面向对象(1)
    理解几种排序方法
    优盘、移动硬盘简便制作启动盘
  • 原文地址:https://www.cnblogs.com/Sinogi/p/8137842.html
Copyright © 2020-2023  润新知