peiwenjun's blog 没有知识的荒原

CF983E NN country 题解

题目描述

给定 \(n\) 个点的树和 \(m\) 条路线,每条路线连接路径 \((u,v)\) 之间的所有点。

\(q\) 次询问,从 \(u\)\(v\) 至少要经过多少条路线。

数据范围

  • \(2\le n\le 2\cdot 10^5,1\le m\le 2\cdot 10^5,1\le q\le 2\cdot 10^5\)

时间限制 \(\texttt{3s}\) ,空间限制 \(\texttt{256MB}\)

分析

容易想到的贪心策略是每一步跳到可到达的深度最小的点。

用倍增加速上述过程,预处理 \(f_{u,i}\) 表示从 \(u\) 出发跳 \(2^i\) 步能到达的最浅点。

什么时候贪心会出问题?记 \(p=lca(u,v)\) ,只要跳完以后仍在子树内就无脑跳(一定最优),如果从 \(x,y\) 再跳一步就要到 \(p\) 及其祖先,但是 \(x,y\) 在同一条路线上,那么只需要一条路径(原策略需要两条)。

注意如果 \((u,v)\) 是一条直上直下的链就不需要考虑这种情况。

于是问题转化为如何判断 \((x,y)\) 是否在同一条路径上,这等价于判断是否存在路线 \((u,v)\) 满足 \(u\)\(x\) 子树内, \(v\)\(y\) 子树内。

\((u,v)\) 看成二维平面上的点 \((dfn_u,dfn_v),(dfn_v,dfn_u)\) ,只需判断横坐标 \([dfn_x,dfn_x+sz_x-1]\) ,纵坐标 \([dfn_y,dfn_y+sz_y-1]\) 的矩形框中是否有点。

这是经典二维数点问题,离线扫描线 \(+\) 树状数组解决。

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

#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+5;
int m,n,p,q,u,v,cnt;
int d[maxn],fa[maxn],sz[maxn],son[maxn];
int dfn[maxn],top[maxn];
int f[maxn][18];
int c[maxn],res[maxn],val[maxn];
vector<int> g[maxn],h[maxn];
struct oper
{
    int l,r,op,id;
};
vector<oper> vec[maxn];
void dfs1(int u)
{
    sz[u]=1;
    for(auto v:g[u])
    {
        d[v]=d[u]+1,fa[v]=u,dfs1(v),sz[u]+=sz[v];
        if(sz[v]>sz[son[u]]) son[u]=v;
    }
}
void dfs2(int u,int f)
{
    dfn[u]=++cnt,top[u]=f;
    if(son[u]) dfs2(son[u],f);
    for(auto v:g[u]) if(v!=son[u]) dfs2(v,v);
}
int lca(int u,int v)
{
    while(top[u]!=top[v]) d[top[u]]>d[top[v]]?u=fa[top[u]]:v=fa[top[v]];
    return d[u]<d[v]?u:v;
}
void add(int x,int v)
{
    while(x<=n) c[x]+=v,x+=x&-x;
}
int query(int x)
{
    int res=0;
    while(x) res+=c[x],x-=x&-x;
    return res;
}
int main()
{
    scanf("%d",&n);
    for(int i=2;i<=n;i++) scanf("%d",&u),g[u].push_back(i);
    d[1]=1,dfs1(1),dfs2(1,1);
    for(int i=1;i<=n;i++) f[i][0]=i;
    scanf("%d",&m);
    while(m--)
    {
        scanf("%d%d",&u,&v),p=lca(u,v);
        f[u][0]=min(f[u][0],p),f[v][0]=min(f[v][0],p);
        h[dfn[u]].push_back(dfn[v]),h[dfn[v]].push_back(dfn[u]);
    }
    for(int u=n;u>=1;u--) for(auto v:g[u]) f[u][0]=min(f[u][0],f[v][0]);
    for(int i=1;i<18;i++) for(int u=1;u<=n;u++) f[u][i]=f[f[u][i-1]][i-1];
    scanf("%d",&q);
    for(int i=1;i<=q;i++)
    {
        scanf("%d%d",&u,&v),p=lca(u,v);
        for(int j=17;j>=0;j--)
        {
            if(f[u][j]>p) u=f[u][j],res[i]+=1<<j;
            if(f[v][j]>p) v=f[v][j],res[i]+=1<<j;
        }
        if(f[u][0]>p||f[v][0]>p) res[i]=-1;
        else
        {
            if(u==p||v==p) res[i]++;
            else
            {
                vec[dfn[u]-1].push_back({dfn[v],dfn[v]+sz[v]-1,-1,i});
                vec[dfn[u]+sz[u]-1].push_back({dfn[v],dfn[v]+sz[v]-1,1,i});
            }
        }
    }
    for(int i=1;i<=n;i++)
    {
        for(auto p:h[i]) add(p,1);
        for(auto [l,r,op,id]:vec[i])
        {
            val[id]+=(query(r)-query(l-1))*op;
            if(op==1) res[id]+=val[id]?1:2;
        }
    }
    for(int i=1;i<=q;i++) printf("%d\n",res[i]);
    return 0;
}

posted on 2025-01-08 19:43  peiwenjun  阅读(17)  评论(0)    收藏  举报

导航