【JZOJ4090】树上三角形(triangle)(LCT or splay)

Problem

给定一个n个点的以1为根的树,每个点有一个正整数点权。
有q个操作,每个操作为以下类型之一:
1. 1 u v 询问树上所有在u到v的简单路径的节点(含u,v)中,是否存在三个不同的节点,使得以这三个节点的点权为边长的三条边能够构成一个三角形。
2. 2 u v 将节点u的权值改成v。
3. 3 u v 若节点v不在以节点u为根的子树里,那么令u的父节点为v,否则令v的父节点为u,如果u=v那么忽略这一条操作。
所有操作按输入的顺序进行。
这个题面应该不会看不懂吧。。

Hint

对于20%的数据,保证n,q<=1000
对于另外30%的数据,保证对于每个操作,t<=2
对于再另外的30%的数据,保证询问操作不超过20次。
对于100%的数据,保证n<=100000 q<=200000 每个节点的权值<=(2^31)-1

Solution

  这道题看上去很诡,是不是?
  一开始,我看到“能够构成一个三角形”就懵逼了,完全不知道怎么做(没有见过这种套路),并且哀怨地想着:如果它是链信息求和或者子树信息求和之类的,我就直接打个LCT切了。
  那么,我们先来思考一下没有3操作该怎么破。

50points:暴力+暴力lca

  首先,设u到v的路径上的点的点权构成的序列为d,设路径长度为d[0]。将d从小到大排序,那么若它不能构成三角形,则必须满足: 1 i d [ 0 ] 2   d [ i ] + d [ i + 1 ] d [ i + 2 ] 。因为对于d[i+1]而言,它最接近且比它大的权值肯定是d[i+2];而在所有比它小的点权当中,d[i]又是最大的。所以如果d[i+1]可以是某个三角形中的第二长的边,那么d[i]+d[i+1]>d[i+2]。
  那么,如果我们在最大权值的限定下,要使这个序列尽量地长,那肯定要使它满足: 1 i d [ 0 ] 2   d [ i ] + d [ i + 1 ] = d [ i + 2 ] 。我们发现这就是个斐波那契数列。大概是第50项以后,斐波那契的值就会>(2^31)-1,也即超过最大权值的限定。所以,如果那个序列长度>50,那肯定会有某个i满足d[i]+d[i+1]>d[i+2],所以肯定可以构成三角形。
  根据上述推论,如果u到v的路径上有超过50个点,那直接判可以。
  所以,我们可以dfs一遍整棵树,然后对于某个询问,我们直接暴力lca,长度一旦超过50就直接退出,否则排序完扫一遍判断。
  然后我们发现这其实就是BZOJ3251。(出题人真会抄袭别人的idea)(切完顺便切掉bzoj的那道题
  时间复杂度:前20points O ( n q ) ,后30points O ( n + 50 q )

100points:LCT or splay

  这道题LCT比较直观,我打的也是LCT。不会LCT的童鞋戳这里
  对于询问操作,我们先让u作为根,access(v),使u、v在一棵splay上,则此时u为该splay上键值最小的点,v为键值最大的点;而又由于平衡树是满足二叉排序树的性质的(右儿子>根>左儿子),所以这棵splay即为u到v的路径。直接在上面dfs求出里面所有的点,如果点数超过50则果断return。
  对于单点修改操作,直接修改即可。
  对于换父亲操作,这里我们碰到了一个难题:那就是要判断v在不在u的子树内。我们可以先以1为根,然后access(v),这样1到v的路径就跑到了一棵splay上;对点u进行splay操作,那么如果点u在1到v的路径上,它也会在那棵splay上。所以如果点u在那条路径上,splay完u后,点1就为该splay上最左(键值最小)的点了。暴力往下跳即可,复杂度即为一次splay的复杂度(经过的点数是一致的):均摊 O ( l o g 2 n )
  然后,假设v在u的子树内,我们就cut掉v到原树上v的父亲(我们还要维护原树的形态,不过只须维护父亲)的边,然后将原树中v的父亲赋为u,并在LCT中Link一条v到u的边。
  时间复杂度: O ( n + q ( l o g 2 n + 50 ) )

Code

#include <cstdio>
#include <cctype>
#include <algorithm>
using namespace std;
#define A son[x][0]
#define B son[x][1]
#define fo(i,a,b) for(i=a;i<=b;i++)
const int N=1e5+1;
int i,n,q,w[N],fa[N],son[N][2],fat[N],t,u,v;
bool tag[N];

void read(int&x)
{
    char ch=getchar();x=0;
    for(;!isdigit(ch);ch=getchar());
    for(;isdigit(ch);x=(x<<3)+(x<<1)+(ch^48),ch=getchar());
}

void push(int x)
{
    if(!tag[x])return;
    if(A)tag[A]=!tag[A],swap(son[A][0],son[A][1]);
    if(B)tag[B]=!tag[B],swap(son[B][0],son[B][1]);
    tag[x]=0;
}
inline bool so(int x){return son[fat[x]][1]==x;}
inline void link(int f,int x,bool d)
{
    if(!x){son[f][d]=0;return;}
    if(fat[x]=f)son[f][d]=x;
}
inline bool if_root(int x){return !fat[x]||son[fat[x]][so(x)]!=x;}
void rotate(int x)
{
    if(!x)return;
    int y=fat[x],z=fat[y],k=so(x),b=son[x][!k];
    link(y,b,k);
    if(!if_root(y))
            link(z,x,so(y));
    else    fat[x]=z;
    link(x,y,!k);
}
int d[N];
void clear(int x)
{
    d[d[0]=1]=x;
    while(!if_root(x))d[++d[0]]=x=fat[x];
    while(d[0])push(d[d[0]--]);
}
void splay(int x)
{
    clear(x);
    for(int f=fat[x];!if_root(x);rotate(x),f=fat[x])
    rotate(!if_root(f)?so(x)==so(f)?f:x:0);
}
void splay(int x,int y)
{
    clear(x);
    for(int f=fat[x];f!=y;rotate(x),f=fat[x])
    rotate(fat[f]!=y?so(x)==so(f)?f:x:0);
}
void access(int y)
{
    int x=0;
    while(y)
    {
        splay(y);
        link(y,x,1);
        x=y;
        y=fat[y];
    }
}
void evert(int x)
{
    tag[x]=!tag[x];
    swap(A,B);
}
void makeroot(int x)
{
    access(x);
    splay(x);
    evert(x);
}
//Link_Cut_Tree

void dfs(int x)
{
    if(!x)return;
    d[++d[0]]=w[x];
    dfs(A);dfs(B);
}
bool check()
{
    d[1]=w[u],d[d[0]=2]=w[v];
    dfs(son[v][!so(v)]);
    if(d[0]<3)return 0;
    sort(d+1,d+d[0]+1);
    int i;
    fo(i,1,d[0]-2)if(d[i]+d[i+1]>d[i+2])return 1;
    return 0;
}

int findlst(int x){while(A)x=A;return x;}
bool if_son(int x,int y)
{
    makeroot(1);
    access(y);
    splay(x);
    return findlst(x)==1;
}

void cut(int x,int y)
{
    makeroot(x);
    access(y);
    splay(x);
    splay(y,x);
    son[x][so(y)]=fat[y]=0;
}
void Link(int x,int y)
{
    makeroot(x);
    fat[x]=y;   
}

int main()
{
    freopen("triangle.in","r",stdin);
    freopen("triangle.out","w",stdout);
    read(n);read(q);
    fo(i,1,n)read(w[i]);
    fo(i,1,n-1)read(fa[i+1]),fat[i+1]=fa[i+1];
    fo(i,1,q)
    {
        read(t);read(u);read(v);
        switch(t)
        {
            case 1:
            {
                if(u==v){printf("N\n");continue;}
                makeroot(u);
                access(v);
                splay(u);
                splay(v,u);
                printf(check()?"Y\n":"N\n");
                break;
            }
            case 2:w[u]=v;break;
            case 3:
            {
                if(u==v)continue;
                if(if_son(u,v))
                        cut(v,fa[v]),Link(v,fa[v]=u);
                else    cut(u,fa[u]),Link(u,fa[u]=v);
            }
        }
    }
}

  我把上面的程序交上去:这里写图片描述
  However,这只是我对未来美好生活的愿景。真实的情况是:这里写图片描述
  我为此修改了无数次,总以为是LCT打错了,调了将近一个小时,终于看到题目的限定条件有一行:每个节点的权值<=(2^31)-1;而我的程序有一行:fo(i,1,d[0]-2)if(d[i]+d[i+1]>d[i+2])return 1;然后我没开ll,然后它就自然溢出了。。。o(╥﹏╥)o
这里写图片描述
  不过这个mistake,c++也要负一半责任——谁叫它算数运算上溢了,不爆RE,而是放任它自然溢出呢!Pascal就很友好。
  正确的代码如下:

#include <cstdio>
#include <cctype>
#include <algorithm>
using namespace std;
#define A son[x][0]
#define B son[x][1]
#define ll long long
#define fo(i,a,b) for(i=a;i<=b;i++)
const int N=1e5+1;
int i,n,q,w[N],fa[N],son[N][2],fat[N],t,u,v;
bool tag[N];

void read(int&x)
{
    char ch=getchar();x=0;
    for(;!isdigit(ch);ch=getchar());
    for(;isdigit(ch);x=(x<<3)+(x<<1)+(ch^48),ch=getchar());
}

void evert(int x)
{
    tag[x]=!tag[x];
    swap(A,B);
}
void push(int x)
{
    if(!tag[x])return;
    if(A)evert(A);
    if(B)evert(B);
    tag[x]=0;
}
inline bool so(int x){return son[fat[x]][1]==x;}
inline void link(int f,int x,bool d)
{
    if(!x){son[f][d]=0;return;}
    if(fat[x]=f)son[f][d]=x;
}
inline bool if_root(int x){return !fat[x]||son[fat[x]][so(x)]!=x;}
void rotate(int x)
{
    if(!x)return;
    int y=fat[x],z=fat[y],k=so(x),b=son[x][!k];
    link(y,b,k);
    if(!if_root(y))
            link(z,x,so(y));
    else    fat[x]=z;
    link(x,y,!k);
}
int d[N];
void clear(int x)
{
    d[d[0]=1]=x;
    while(!if_root(x))d[++d[0]]=x=fat[x];
    while(d[0])push(d[d[0]--]);
}
void splay(int x)
{
    clear(x);
    for(int f=fat[x];!if_root(x);rotate(x),f=fat[x])
    rotate(!if_root(f)?so(x)==so(f)?f:x:0);
}
void access(int y)
{
    int x=0;
    while(y)
    {
        splay(y);
        link(y,x,1);
        x=y;
        y=fat[y];
    }
}
void makeroot(int x)
{
    access(x);
    splay(x);
    evert(x);
}
//Link_Cut_Tree

void dfs(int x)
{
    if(!x)return;
    d[++d[0]]=w[x];
    if(d[0]>50)return;
    dfs(A);
    if(d[0]>50)return;
    dfs(B);
}
bool query()
{
    makeroot(u);
    access(v);
    splay(v);
    d[0]=0;
    dfs(v);
    if(d[0]>50)return 1;
    sort(d+1,d+d[0]+1);
    int i;
    fo(i,1,d[0]-2)if((ll)d[i]+(ll)d[i+1]>(ll)d[i+2])return 1;
    return 0;
}

int findlst(int x){while(A)x=A;return x;}
bool if_son(int x,int y)
{
    makeroot(1);
    access(y);
    splay(x);
    return findlst(x)==1;
}

void cut(int x,int y)
{
    makeroot(x);
    access(y);
    splay(y);
    son[y][0]=fat[x]=0;
}
void Link(int x,int y)
{
    makeroot(x);
    fat[x]=y;   
}

int main()
{
    freopen("triangle.in","r",stdin);
    freopen("triangle.out","w",stdout);
    read(n);read(q);
    fo(i,1,n)read(w[i]);
    fo(i,1,n-1)read(fa[i+1]),fat[i+1]=fa[i+1];
    fo(i,1,q)
    {
        read(t);read(u);read(v);
        switch(t)
        {
            case 1:
            {
                if(u==v){printf("N\n");continue;}
                printf(query()?"Y\n":"N\n");
                break;
            }
            case 2:w[u]=v;break;
            case 3:
            {
                if(u==v)continue;
                if(if_son(u,v))
                        cut(v,fa[v]),Link(v,fa[v]=u);
                else    cut(u,fa[u]),Link(u,fa[u]=v);
                break;
            }
        }
    }
}

猜你喜欢

转载自blog.csdn.net/qq_36551189/article/details/80315013
今日推荐