洛谷P3348 [ZJOI2016]大森林 [LCT]

传送门


刷了那么久水题之后终于有一题可以来写写博客了。

但是这题太神仙了我还没完全弄懂……

upd:写完博客之后似乎懂了。


思路

首先很容易想到\(O(n^2\log n)\)乘上\(O(\frac{n}{\log n})\)的巨大常数的暴力做法(雾

然后可以发现这题支持把询问抽离出来最后做,那么我们可以先只关注修改操作。

可以发现一个点在\([l,r]\)的树上连上去和在所有树上都连上去其实没有太大区别,只是修改生长节点时要判一下是否存在,其他时候其实可以每一棵树上都连一个,因为不存在的点并不会被询问到。

发现这题主要恶心在1操作(也就是更换生长节点),如果没有1操作那么所有树的形态就都一样了。

考虑用某种方法使得我们可以只维护一棵树,然后在这一棵树上面实现从\(1\)\(n\)的灵活转换。

其实也不用太过灵活,只需要能够从\(i\)快速推到\(i+1\)即可。

既然1操作恶心,那么我们重点分析1操作。

考虑\([l,r],x\)的这样一次操作,对\(l,l-1\)这两棵树会造成什么差异。假设\(l-1\)的生长节点为\(y\)

那么这就相当于把接下来所有连在\(y\)上面的节点以及它们的子树全都接到\(x\)上。(注意:不管它们是否真的有在\(x\)树上,这对答案没有影响

同理,\(r,r+1\)之间的差异相当于接回去。

那么我们只要迅速地修改一堆子树的父亲即可。

考虑在一个虚点下面连上这一堆子树,那么每次就只需要修改虚点的父亲就好了。

每当有一个新的1操作\([l,r],x\)时就新开一个虚点\(p\),表示接下来所有0操作的点都连到\(p\)上,同时\(p\)也要连到上一个虚点上,保证之前的虚点也连上了所有点。

\(p\)\(l\)树之前一直连在原虚点上,\([l,r]\)时连在\(x\)上,之后再连回去。

然后就可以从\(1\)\(n\)处理这棵树上的修改和询问,然后就做完了。

我不敢确定自己是否真的理解了做法,请发现思路上的错误的大佬指正,谢谢!


代码

注意这里没有保存一个点在原树上的父亲是谁,所以不能随便\(\text{makeroot}\)破坏LCT上的父子结构。

但是我还是不怎么明白\(\text{cut}\)函数中为什么\(lson(x)\)一定是\(x\)在原树中的父亲,希望有大佬赐教。

#include<bits/stdc++.h>
clock_t t=clock();
namespace my_std{
    using namespace std;
    #define pii pair<int,int>
    #define fir first
    #define sec second
    #define MP make_pair
    #define rep(i,x,y) for (int i=(x);i<=(y);i++)
    #define drep(i,x,y) for (int i=(x);i>=(y);i--)
    #define go(x) for (int i=head[x];i;i=edge[i].nxt)
    #define templ template<typename T>
    #define sz 404040
    typedef long long ll;
    typedef double db;
    mt19937 rng(chrono::steady_clock::now().time_since_epoch().count());
    templ inline T rnd(T l,T r) {return uniform_int_distribution<T>(l,r)(rng);}
    templ inline bool chkmax(T &x,T y){return x<y?x=y,1:0;}
    templ inline bool chkmin(T &x,T y){return x>y?x=y,1:0;}
    templ inline void read(T& t)
    {
        t=0;char f=0,ch=getchar();double d=0.1;
        while(ch>'9'||ch<'0') f|=(ch=='-'),ch=getchar();
        while(ch<='9'&&ch>='0') t=t*10+ch-48,ch=getchar();
        if(ch=='.'){ch=getchar();while(ch<='9'&&ch>='0') t+=d*(ch^48),d*=0.1,ch=getchar();}
        t=(f?-t:t);
    }
    template<typename T,typename... Args>inline void read(T& t,Args&... args){read(t); read(args...);}
    char __sr[1<<21],__z[20];int __C=-1,__zz=0;
    inline void Ot(){fwrite(__sr,1,__C+1,stdout),__C=-1;}
    inline void print(register int x)
    {
        if(__C>1<<20)Ot();if(x<0)__sr[++__C]='-',x=-x;
        while(__z[++__zz]=x%10+48,x/=10);
        while(__sr[++__C]=__z[__zz],--__zz);__sr[++__C]='\n';
    }
    void file()
    {
        #ifndef ONLINE_JUDGE
        freopen("a.in","r",stdin);
        #endif
    }
    inline void chktime()
    {
        #ifndef ONLINE_JUDGE
        cout<<(clock()-t)/1000.0<<'\n';
        #endif
    }
    #ifdef mod
    ll ksm(ll x,int y){ll ret=1;for (;y;y>>=1,x=x*x%mod) if (y&1) ret=ret*x%mod;return ret;}
    ll inv(ll x){return ksm(x,mod-2);}
    #else
    ll ksm(ll x,int y){ll ret=1;for (;y;y>>=1,x=x*x) if (y&1) ret=ret*x;return ret;}
    #endif
//  inline ll mul(ll a,ll b){ll d=(ll)(a*(double)b/mod+0.5);ll ret=a*b-d*mod;if (ret<0) ret+=mod;return ret;}
}
using namespace my_std;

int n,m;

int fa[sz],ch[sz][2],w[sz],sum[sz];
#define ls ch[x][0]
#define rs ch[x][1]
void pushup(int x){sum[x]=w[x]+sum[ls]+sum[rs];}
bool get(int x){return ch[fa[x]][1]==x;}
bool nroot(int x){return ch[fa[x]][0]==x||ch[fa[x]][1]==x;}
void rotate(int x)
{
    int y=fa[x],z=fa[y],k=get(x),w=ch[x][!k];
    if (nroot(y)) ch[z][get(y)]=x;ch[x][!k]=y;ch[y][k]=w;
    if (w) fa[w]=y;fa[y]=x;fa[x]=z;
    pushup(y);pushup(x);
}
void splay(int x){for (int y;(y=fa[x],nroot(x));rotate(x)) if (nroot(y)) rotate(get(x)==get(y)?y:x);}
int access(int x){int y=0;for (;x;x=fa[y=x]) splay(x),rs=y,pushup(x);return y;}
void link(int x,int y){splay(x);fa[x]=y;}
void cut(int x){access(x);splay(x);fa[ls]=0;ls=0;pushup(x);}
int query(int x,int y)
{
    int ret=0;
    access(x);splay(x);ret+=sum[x];
    int lca=access(y);
    splay(y);
    ret+=sum[y];
    access(lca);splay(lca);ret-=2*sum[lca];
    return ret;
}

int L[sz],R[sz]; // x点存在的区间
int pos[sz]; // x在LCT中的编号

struct hh
{
    int pos,x,y,id;
    const bool operator < (const hh &y) const { return pos==y.pos?id<y.id:pos<y.pos; }
}q[sz];int c;

int ans[sz];

int main()
{
    file();
    read(n,m);
    int ima,real,cnt; // 当前虚点、实点编号,总编号 
    ima=2;real=1;cnt=2;
    L[1]=1;R[1]=n;pos[1]=1;w[1]=sum[1]=1;
    link(2,1);
    int opt,x,y,z,l,r;
    rep(i,1,m)
    {
        read(opt);
        if (opt==0)
        {
            read(l,r);
            x=++real;pos[x]=++cnt;w[pos[x]]=sum[pos[x]]=1;
            link(pos[x],ima);
            L[x]=l,R[x]=r;
        }
        else if (opt==1)
        {
            read(l,r,x);
            l=max(l,L[x]);r=min(r,R[x]);
            if (l>r) continue;
            z=++cnt;link(z,ima);
            q[++c]=(hh){l,z,pos[x],i-m};
            q[++c]=(hh){r+1,z,ima,i-m};
            ima=z;
        }
        else read(z,x,y),q[++c]=(hh){z,pos[x],pos[y],i};
    }
    sort(q+1,q+c+1);
    memset(ans,-1,sizeof(ans));
    rep(i,1,c) 
        if (q[i].id<=0) cut(q[i].x),link(q[i].x,q[i].y);
        else ans[q[i].id]=query(q[i].x,q[i].y);
    rep(i,1,m) if (ans[i]!=-1) printf("%d\n",ans[i]);
    return 0;
}

转载于:https://www.cnblogs.com/p-b-p-b/p/10660155.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值