CF #526 div.2 F. Max Mex //lca(dfs序+st表实现)+线段树合并树上路径

题目

https://codeforces.com/contest/1084/problem/F

题意

给一棵树t,t上结点用[0,n)的排列p[]标号。
有两种询问:
1.交换结点i和结点j的标号。
2.求t上一条路径,包括[0,k]的所有数,要求k尽量大,输出这个最大的k。
q < 2 e 5 , n < 2 e 5 q<2e5,n<2e5 q<2e5,n<2e5

思路

大致思路就是用线段树维护标号为[l,r]的结点是不是在一条路径上。
首先需要维护lca:
这里用dfs序+st表维护原始树的lca(讯问中交换两个结点的序号,可以映射回原来的树的结点),因为线段树合并的时候需要多次询问lca,为了让复杂度少个log。
线段树维护:
[l,r]的标号是否在一条路径上,[l,r]的标号所在的路径的左端点,右端点。
合并两条路径:
先拿出左右儿子路径的4个点(也有可能是3个)中距离最远的两个点(这一部分用lca就可以求出),然后考虑所有点是不是都在这两个点的路径上。如果在,说明可以合并,否则不能。
询问的时候:
思路就是贪心,先考虑左边一段能不能合并到总的一段,如果可以就query右边一段,否则就query左边一段,到线段树叶子结点时,考虑是否能并于总的一段即可。我一开始写二分(md二分根本t爆的…),其实O(log)就可以询问。
注意st表,标号,dfn序的映射,映射我写的也太差了。
总复杂度 O ( ( n + q ) l o g n ∗ 16 ∗ k ) k O((n+q)logn*16*k)k O((n+q)logn16k)k是线段树常数,16是找出2条路径端点中最远的两个端点。
这题写一天…差不多用尽毕生所学…2916ms/3000ms(自己写的常数很大)

int ju(int a,int b,int c){ //判断c是不是在a->b的路径上。
    int lca=LCA(a,b);
    if(LCA(c,lca)==lca&&(LCA(c,b)==c||LCA(c,a)==c)) return 1;
    return 0;
}
/*   Author : Rshs   */
#include<bits/stdc++.h>
using namespace std;
#define FI first
#define SE second
#define LL long long
#define LDB long double
#define MP make_pair
#define PII pair<int,int>
#define SZ(a) (int)a.size()
const LDB pai = acos(-1.0L);
const LDB eps = 1e-10;
const LL mod = 1e9+7;
const int MXN = 2e5+5;
const int logMXN = 25;
int p[MXN],n,fa[MXN];
vector<int>g[MXN];
int dfs_clock=0;
int dfn[MXN*2],de[MXN],fst[MXN],id[MXN];
int st[MXN*2][logMXN],lgg[MXN*2];
int L[MXN*4],R[MXN*4],T[MXN*4];
void dfs(int now,int pre){
    dfn[++dfs_clock]=now;
    fst[now]=dfs_clock;
    for(auto i:g[now]){
        if(i==pre) continue;
        de[i]=de[now]+1;
        dfs(i,now);
        dfn[++dfs_clock]=now;
    }
}
void ST_build(int nn){
    for(int i=1;i<=nn;i++) st[i][0]=i;
    for(int j=1;(1<<j)<=nn;j++){
        for(int i=1;i+(1<<j)-1<=nn;i++){
            if(de[dfn[st[i][j-1]]]<de[dfn[st[i+(1<<(j-1))][j-1]]]) st[i][j]=st[i][j-1];
            else st[i][j]=st[i+(1<<(j-1))][j-1];
        }
    }
    for(int i=1;i<=nn;i++) lgg[i]=(int)(log2((double)(i)));
}
int LCA(int tl,int tr){
    int l=fst[id[tl]],r=fst[id[tr]];
    if(l>r)swap(l,r);
    int lloo=lgg[r-l+1];
    if(de[dfn[st[l][lloo]]]<de[dfn[st[r-(1<<lloo)+1][lloo]]]) return p[dfn[st[l][lloo]]];
    return p[dfn[st[r-(1<<lloo)+1][lloo]]];
}

int ju(int a,int b,int c){ //cÔÚa->b ÉÏ
    int lca=LCA(a,b);
    if(LCA(c,lca)==lca&&(LCA(c,b)==c||LCA(c,a)==c)) return 1;
    return 0;
}
pair<int,pair<int,int>> ck(int a,int b,int c,int d){
    if(c==-1)return MP(1,MP(a,b));
    vector<int>v;
    v.push_back(a);v.push_back(b);
    v.push_back(c);v.push_back(d);
    sort(v.begin(),v.end());
    v.erase(unique(v.begin(),v.end()),v.end());
    int tl,tr,di=-1;
    for(auto i:v){
        for(auto j:v){
            int lca=LCA(i,j);
            int zz=-de[id[lca]]*2+de[id[i]]+de[id[j]];
            if(di<zz) di=zz,tl=i,tr=j;
        }
    }
    for(int i=0;i<SZ(v);i++) if(ju(tl,tr,v[i])==0){return MP(0,MP(0,0));}
    return MP(1,MP(tl,tr));
}
void update(int l,int r,int rt,int pos){
    if(l==r){
        T[rt]=1;
        L[rt]=l,R[rt]=r;
        return ;

    }
    int m=(l+r)/2;
    if(pos<=m) update(l,m,rt<<1,pos);
    if(pos>m) update(m+1,r,rt<<1|1,pos);
    if(T[rt<<1]==0||T[rt<<1|1]==0) {
        T[rt]=0;return ;
    }
    auto f=ck(L[rt<<1],R[rt<<1],L[rt<<1|1],R[rt<<1|1]);
    if(f.FI) {
        T[rt]=1;
        L[rt]=f.SE.FI,R[rt]=f.SE.SE;
        return ;
    }
    T[rt]=0;
}
int LLL=-1,RRR=-1;
int query(int l,int r,int rt){
    if(l==r){
        auto f=ck(L[rt],R[rt],LLL,RRR);
        if(f.FI) {
            LLL=f.SE.FI;
            RRR=f.SE.SE;
            return 1;
        }
        else return 0;
    }
    int mid=(l+r)/2;
    int ls=rt<<1,rs=rt<<1|1;
    auto f=ck(L[ls],R[ls],LLL,RRR);
    if(T[ls]==0) return query(l,mid,ls);
    if(T[ls]==1&&f.FI) {
        LLL=f.SE.FI;
        RRR=f.SE.SE;
        return (mid-l+1)+query(mid+1,r,rs);
    }
    else {
        return query(l,mid,ls);
    }
}
int main(){
    cin>>n;
    for(int i=1;i<=n;i++)scanf("%d",&p[i]);
    for(int i=1;i<=n;i++) id[p[i]]=i;
    for(int i=2;i<=n;i++) {
        scanf("%d",&fa[i]);
        g[fa[i]].push_back(i);g[i].push_back(fa[i]);
    }
    de[1]=1;
    dfs(1,-1);
    ST_build(dfs_clock);
    //cout<<LCA(2,4)<<'\n';
    for(int i=0;i<n;i++)
        update(0,n-1,1,i);
    int q;cin>>q;
    while(q--){
        int op;scanf("%d",&op);
        if(op==2) {
            LLL =-1,RRR=-1;
            cout<<query(0,n-1,1)<<'\n';
        }
        if(op==1){
            int sa,sb;scanf("%d%d",&sa,&sb);
            swap(id[p[sa]],id[p[sb]]);
            swap(p[sa],p[sb]);
            update(0,n-1,1,p[sa]);
            update(0,n-1,1,p[sb]);
        }
    }
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值