peiwenjun's blog 没有知识的荒原

P4175 [CTSC2008]网络管理 题解

题目描述

给定一棵 \(n\) 个节点的树,点有点权 \(w_i\)\(q\) 次操作:

  • 0 u v :将节点 \(u\) 的权值修改为 \(v\)
  • k u v :查询 \(u\to v\) 路径上第\(k\)大的点权,如果路径上不足 \(k\) 个点输出 invalid request!

数据范围

  • \(1\le n,q\le 8\cdot 10^4,1\le k\le n,1\le w_i\le 10^8\)

时间限制 \(\texttt{2s}\) ,空间限制 \(\texttt{500MB}\)

分析

如果树退化成链,序列上区间第 \(k\) 大有两种经典做法:主席树或整体二分。

事实上这两种做法略加修改都可以解决本题。

树套树

主席树的瓶颈在于无法带修,考虑用树套树维护。

\(lca(u,v)=p\) ,那么 \(u\to v\) 的路径可以看成 \((u\to1)+(v\to1)-(p\to1)-(fa_p\to1)\) ,只需维护每个点到根的路径上所有点权的信息。

每次修改可以看成给原树的一棵子树删掉一个权值为 \(w_i\) 的点,再加入一个权值为 \(v\) 的点。

\(dfs\) 序上就是对区间进行操作,差分一下变成单点修改前缀求和,用树状数组套权值线段树实现。

查询时线段树二分,将 \(\mathcal O(\log n)\) 棵权值线段树的根节点同时向左或向右移动即可。

记得离散化,否则两只 \(\log\) 的空间存不下。

时间复杂度 \(\mathcal O((n+q)\log^2n)\)

#include<bits/stdc++.h>
using namespace std;
const int maxn=8e4+5;
int m,n,q,u,v,cnt,tot;
int d[maxn],fa[maxn][17];
int w[maxn],sz[maxn],dfn[maxn];
int c[2*maxn],rt[maxn];
vector<int> g[maxn];
struct oper
{
    int k,u,v;
}t[maxn];
struct node
{
    int ls,rs,sum;
}f[500*maxn];
void dfs(int u,int f)
{
    sz[u]=1,dfn[u]=++cnt;
    for(auto v:g[u])
    {
        if(v==f) continue;
        d[v]=d[u]+1,fa[v][0]=u;
        for(int i=1;i<=16;i++) fa[v][i]=fa[fa[v][i-1]][i-1];
        dfs(v,u),sz[u]+=sz[v];
    }
}
int lca(int u,int v)
{
    if(d[u]<d[v]) swap(u,v);
    for(int i=16;i>=0;i--)
        if(d[fa[u][i]]>=d[v])
            u=fa[u][i];
    if(u==v) return u;
    for(int i=16;i>=0;i--)
        if(fa[u][i]!=fa[v][i])
            u=fa[u][i],v=fa[v][i];
    return fa[u][0];
}
void pushup(int p)
{
    f[p].sum=f[f[p].ls].sum+f[f[p].rs].sum;
}
void modify(int &p,int l,int r,int pos,int val)
{
    if(!p) p=++tot;
    if(l==r) return f[p].sum+=val,void();
    int mid=(l+r)/2;
    if(pos<=mid) modify(f[p].ls,l,mid,pos,val);
    else modify(f[p].rs,mid+1,r,pos,val);
    pushup(p);
}
void add(int x,int v,int op)
{
    while(x<=n) modify(rt[x],1,m,v,op),x+=x&(-x);
}
void ask(int x,vector<int> &vec)
{
    while(x) vec.push_back(rt[x]),x-=x&(-x);
}
int query(int a,int b,int c,int d,int k)
{
    vector<int> v1,v2;
    ask(dfn[a],v1),ask(dfn[b],v1),ask(dfn[c],v2),ask(dfn[d],v2);
    int l=1,r=m;
    while(l!=r)
    {
        int cur=0,mid=(l+r)/2;
        for(auto &p:v1) cur+=f[f[p].ls].sum;
        for(auto &p:v2) cur-=f[f[p].ls].sum;
        if(cur>=k)
        {
            r=mid;
            for(auto &p:v1) p=f[p].ls;
            for(auto &p:v2) p=f[p].ls;
        }
        else
        {
            l=mid+1,k-=cur;
            for(auto &p:v1) p=f[p].rs;
            for(auto &p:v2) p=f[p].rs;
        }
    }
    return l;
}
int main()
{
    scanf("%d%d",&n,&q);
    for(int i=1;i<=n;i++) scanf("%d",&w[i]),c[++m]=w[i];
    for(int i=1;i<=n-1;i++)
    {
        scanf("%d%d",&u,&v);
        g[u].push_back(v),g[v].push_back(u);
    }
    d[1]=1,dfs(1,0);
    for(int i=1;i<=q;i++)
    {
        scanf("%d%d%d",&t[i].k,&t[i].u,&t[i].v);
        if(!t[i].k) c[++m]=t[i].v;
    }
    sort(c+1,c+m+1);
    m=unique(c+1,c+m+1)-c-1;
    for(int i=1;i<=n;i++)
    {
        w[i]=lower_bound(c+1,c+m+1,w[i])-c;
        add(dfn[i],w[i],1),add(dfn[i]+sz[i],w[i],-1);
    }
    for(int i=1;i<=q;i++)
    {
        int k=t[i].k,u=t[i].u,v=t[i].v;
        if(!k)
        {
            add(dfn[u],w[u],-1),add(dfn[u]+sz[u],w[u],1);
            w[u]=lower_bound(c+1,c+m+1,v)-c;
            add(dfn[u],w[u],1),add(dfn[u]+sz[u],w[u],-1);
        }
        else
        {
            int p=lca(u,v),dis=d[u]+d[v]-2*d[p]+1;
            if(k>dis) printf("invalid request!\n");
            else printf("%d\n",c[query(u,v,p,fa[p][0],dis-k+1)]);
        }
    }
    return 0;
}
整体二分

整体二分天然支持带修,所以相比之下码量会小很多。

整体二分为什么支持带修:

整体二分本质上是对值域做 \(cdq\) 分治。以动态区间第 \(k\)为例:

  • 如果修改和询问都落在 \(mid\) 同一侧,直接往下递归即可。
  • 如果修改 \(\le mid\) 但询问 \(\gt mid\) ,我们需要计算 \(\le mid\) 的修改对询问的贡献。

修改和询问还有时间维度上的偏序关系(前面的修改对后面的询问才会产生贡献),但刚好整体二分会顺序扫描所有操作,所以时间维度无需担心。

二分以后我们的操作会变成:单点加一减一,路径求和。

维护每个点到根的路径答案,那么单点加会对一棵子树产生贡献。

通过 \(dfs\) 序变成区间加,单点求值,差分树状数组解决。

时间复杂度 \(\mathcal O((n+q)\log n\log V)\)

#include<bits/stdc++.h>
using namespace std;
const int maxn=8e4+5;
int k,m,n,q,u,v,cnt;
int d[maxn],fa[maxn][17];
int sz[maxn],dfn[maxn];
int c[maxn],w[maxn],res[maxn];
vector<int> g[maxn];
struct oper
{
    int k,p,u,v,id;
    ///id==0,k=1/-1表示加入/删除第u个点权值为v的贡献
    ///id!=0,询问u->v路径第k小,p=lca(u,v),编号为id
};
vector<oper> vec;
void dfs(int u,int f)
{
    sz[u]=1,dfn[u]=++cnt;
    for(auto v:g[u])
    {
        if(v==f) continue;
        d[v]=d[u]+1,fa[v][0]=u;
        for(int i=1;i<=16;i++) fa[v][i]=fa[fa[v][i-1]][i-1];
        dfs(v,u),sz[u]+=sz[v];
    }
}
int lca(int u,int v)
{
    if(d[u]<d[v]) swap(u,v);
    for(int i=16;i>=0;i--)
        if(d[fa[u][i]]>=d[v])
            u=fa[u][i];
    if(u==v) return u;
    for(int i=16;i>=0;i--)
        if(fa[u][i]!=fa[v][i])
            u=fa[u][i],v=fa[v][i];
    return fa[u][0];
}
void add(int x,int v)
{
    while(x<=n) c[x]+=v,x+=x&(-x);
}
int ask(int x)
{
    int res=0;
    while(x) res+=c[x],x-=x&(-x);
    return res;
}
void solve(int l,int r,vector<oper> vec)
{
    if(vec.empty()) return ;
    if(l==r)
    {
        for(auto p:vec) res[p.id]=l;
        return ;
    }
    int mid=(l+r)/2;
    vector<oper> v1,v2;
    for(auto p:vec)
    {
        int &k=p.k,&u=p.u,&v=p.v;
        if(!p.id)
        {
            if(v<=mid) add(dfn[u],k),add(dfn[u]+sz[u],-k),v1.push_back(p);
            else v2.push_back(p);
        }
        else
        {
            int cur=ask(dfn[u])+ask(dfn[v])-ask(dfn[p.p])-ask(dfn[fa[p.p][0]]);
            if(cur>=k) v1.push_back(p);
            else k-=cur,v2.push_back(p);
        }
    }
    for(auto p:v1)
    {
        int k=p.k,u=p.u;
        if(!p.id) add(dfn[u],-k),add(dfn[u]+sz[u],k);
    }
    solve(l,mid,v1);
    solve(mid+1,r,v2);
}
int main()
{
    scanf("%d%d",&n,&q);
    for(int i=1;i<=n;i++) scanf("%d",&w[i]),vec.push_back({1,0,i,w[i],0});
    for(int i=1;i<=n-1;i++)
    {
        scanf("%d%d",&u,&v);
        g[u].push_back(v),g[v].push_back(u);
    }
    d[1]=1,dfs(1,0);
    while(q--)
    {
        scanf("%d%d%d",&k,&u,&v);
        if(!k) vec.push_back({-1,0,u,w[u],0}),w[u]=v,vec.push_back({1,0,u,w[u],0});
        else
        {
            int p=lca(u,v),dis=d[u]+d[v]-2*d[p]+1;
            if(k>dis) res[++m]=-1;
            else vec.push_back({dis-k+1,p,u,v,++m});
        }
    }
    solve(1,1e8,vec);
    for(int i=1;i<=m;i++)
        if(res[i]!=-1) printf("%d\n",res[i]);
        else printf("invalid request!\n");
    return 0;
}
树上带修莫队

其实这道题还可以用树上带修莫队做。

按照树上莫队的套路,先用欧拉序将树上路径变成区间内出现奇数次的数。

我们希望维护这个集合,考虑值域分块。

这样修改是 \(\mathcal O(1)\) 的,查询时先扫一遍可以求出第 \(k\) 小数所在块,再扫一遍即可求出具体值,时间复杂度 \(\mathcal O(\sqrt n)\)

处理修改多维护一维时间轴就可以了。

时间复杂度 \(\mathcal O(n^\frac 43q^\frac 13+q\sqrt n)\)

#include<bits/stdc++.h>
#define fi first
#define se second
#define mp make_pair
#define pii pair<int,int>
using namespace std;
const int maxn=1.6e5+5,B=2000;
int l,m,n,q,r,t,u,v,cnt;
int d[maxn],fa[maxn][17];
int c[maxn],w[maxn],bel[maxn],res[maxn];
int id[maxn],st[maxn],ed[maxn];
pii h[maxn];
vector<int> g[maxn];
struct oper
{
    int k,u,v;
}f[maxn];
struct quer
{
    int k,p,l,r,t,id;
};
vector<quer> vec;
void dfs(int u,int f)
{
    st[u]=++cnt,id[cnt]=u;
    for(auto v:g[u])
    {
        if(v==f) continue;
        d[v]=d[u]+1,fa[v][0]=u;
        for(int i=1;i<=16;i++) fa[v][i]=fa[fa[v][i-1]][i-1];
        dfs(v,u);
    }
    ed[u]=++cnt,id[cnt]=u;
}
int lca(int u,int v)
{
    if(d[u]<d[v]) swap(u,v);
    for(int i=16;i>=0;i--)
        if(d[fa[u][i]]>=d[v])
            u=fa[u][i];
    if(u==v) return u;
    for(int i=16;i>=0;i--)
        if(fa[u][i]!=fa[v][i])
            u=fa[u][i],v=fa[v][i];
    return fa[u][0];
}
bool cmp(quer a,quer b)
{
    if(bel[a.l]!=bel[b.l]) return bel[a.l]<bel[b.l];
    if(bel[a.r]!=bel[b.r]) return bel[a.r]<bel[b.r];
    return a.t<b.t;
}
namespace block
{
    const int B=400;
    int n;
    int a[maxn],bel[maxn],cnt[maxn];
    int st[maxn],ed[maxn];
    bool vis[maxn];
    void init(int _n)
    {
        n=_n;
        for(int i=1;i<=n;i++) bel[i]=(i-1)/B+1;
        for(int i=1;i<=bel[n];i++) st[i]=(i-1)*B+1,ed[i]=min(i*B,n);
    }
    void add(int x)
    {
        int op=vis[x]?-1:1;
        a[w[x]]+=op,cnt[bel[w[x]]]+=op,vis[x]^=1;
    }
    void work(int t)
    {
        int &x=h[t].fi,&y=h[t].se;
        if(vis[x]) add(x),swap(w[x],y),add(x);
        else swap(w[x],y);
    }
    int query(int k)
    {
        for(int i=1;i<=bel[n];i++)
        {
            if(k>cnt[i]) k-=cnt[i];
            else
            {
                for(int j=st[i];j<=ed[i];j++)
                    if(k>a[j]) k-=a[j];
                    else return j;
                return assert(false),-1;
            }
        }
        return assert(false),-1;
    }
}
using block::add;
using block::work;
int main()
{
    scanf("%d%d",&n,&q);
    for(int i=1;i<=n;i++) scanf("%d",&w[i]);
    for(int i=1;i<=n-1;i++)
    {
        scanf("%d%d",&u,&v);
        g[u].push_back(v),g[v].push_back(u);
    }
    d[1]=1,dfs(1,0),cnt=0;
    for(int i=1;i<=n;i++) c[++cnt]=w[i];
    for(int i=1;i<=2*n;i++) bel[i]=(i-1)/B+1;
    for(int i=1;i<=q;i++)
    {
        scanf("%d%d%d",&f[i].k,&f[i].u,&f[i].v);
        if(!f[i].k) c[++cnt]=f[i].v;
    }
    sort(c+1,c+cnt+1);
    cnt=unique(c+1,c+cnt+1)-c-1;
    for(int i=1;i<=n;i++) w[i]=lower_bound(c+1,c+cnt+1,w[i])-c;
    for(int i=1;i<=q;i++)
    {
        int k=f[i].k,u=f[i].u,v=f[i].v;
        if(!k) h[++t]=mp(u,lower_bound(c+1,c+cnt+1,v)-c);
        else
        {
            if(st[u]>st[v]) swap(u,v);
            int p=lca(u,v),dis=d[u]+d[v]-2*d[p]+1;
            m++;
            if(k>dis) res[m]=-1;
            else if(u==p) vec.push_back({dis-k+1,0,st[u],st[v],t,m});
            else vec.push_back({dis-k+1,p,ed[u],st[v],t,m});
        }
    }
    sort(vec.begin(),vec.end(),cmp);
    l=1,r=0,t=0,block::init(cnt);
    for(auto p:vec)
    {
        while(l>p.l) add(id[--l]);
        while(r<p.r) add(id[++r]);
        while(l<p.l) add(id[l++]);
        while(r>p.r) add(id[r--]);
        while(t<p.t) work(++t);
        while(t>p.t) work(t--);
        if(p.p) add(p.p);
        res[p.id]=block::query(p.k);
        if(p.p) add(p.p);
    }
    for(int i=1;i<=m;i++)
        if(res[i]!=-1) printf("%d\n",c[res[i]]);
        else printf("invalid request!\n");
    return 0;
}

posted on 2023-03-31 16:59  peiwenjun  阅读(18)  评论(0)    收藏  举报

导航