【UOJ#53】【UR#4】追击圣诞老人

树链剖分,堆

题目大意:

给定一棵 $n$ 个点的树,每个点有正的点权 $w_i$。

定义一条路径 $(p_1,p_2,p_3,\ldots,p_l)$ 的代价为 $\sum\limits_{i=1}^l w_{p_i}$。

对于一个点 $u$,有参数 $a_u,b_u,c_u$,只有树上处于 $a_u,b_u,c_u$ 三个点两两最短路上的点才是 $u$ 能到达的点。

问代价前 $k$ 小的路径的代价。

空间限制较紧。

题解:

不难想到用堆维护路径的代价,每次取出最小的那个,并在此基础上加以扩展后再放入堆里。这样取出来的前 $k$ 个即为答案。

但是一个点能到的点的个数是很多的,肯定不能把所有转移都放进堆里。

对于一个关于点 $u$ 的状态的后继状态,我们一定是先取出它能转移到的点中 $w_i$ 最小的那个,然后是次小的,依此类推。我们希望只将 $u$ 后继状态的最小状态放入堆中,等取出的时候再考虑把次小的状态加入,等等。

考虑 $u$ 能到的点是一条链的情况。对于链 $(a,b)$,其 $w$ 最小的点是 $k$,我们在选了 $k$ 之后,可以将 $(a,b)$ 变成 $(a,k)$ 和 $(b,k)$ 两条链(不含 $k$ 这个端点),然后在两条链中分别找最小值再插入堆中。这样一个状态取出时,新加入的状态是 $O(1)$ 的。

现在 $u$ 能到的点是包含其中三个点的连通块,我们可以简单地将其分成三条链来处理。

为了使计算过程简便,可以将链都拆成从下往上的(即 $(a,b)$ 满足其中一个端点是另一个端点的祖先)。

那么接下来就是要求链上的最值问题了。由于本题的数据范围以及时空限制,倍增、ST 表的空间复杂度无法接受。因此考虑树链剖分。

这里如果对每段链都进行线段树询问,则时间复杂度为 $O(k\log^2 n)$,难以通过。注意到除了最后一段,我们每次跳重链的时候都是跳了这段重链的一个前缀。因此对每个重链的前缀记录一下最小的那个点,然后中间这部分就可以单次 $O(1)$ 了。最后那段用线段树即可。

时间复杂度 $O((n+k)\log n)$。空间复杂度 $O(n+k)$。

Code:

#include<iostream>
#include<algorithm>
#include<queue>
#include<utility>
using namespace std;
const int N=5e5+5;
int n,k,w[N],fa[N],head[N],nxt[N],mn[N];
pair<int,int>A[N][3];
int sz[N],son[N],dep[N],top[N],dfn[N],idx,idfn[N];
int _mn[N<<2];
inline int _min(int a,int b){return w[a]<w[b]?a:b;}
void dfs(int now){
    sz[now]=1;
    for(int to=head[now];to;to=nxt[to]){
        dep[to]=dep[now]+1;
        dfs(to);
        sz[now]+=sz[to];
        if(!son[now]||sz[son[now]]<sz[to])son[now]=to;
    }
}
void dfs2(int now){
    idfn[dfn[now]=++idx]=now;
    if(son[now])top[son[now]]=top[now],mn[son[now]]=_min(mn[now],son[now]),dfs2(son[now]);
    for(int to=head[now];to;to=nxt[to])if(to!=son[now])top[to]=mn[to]=to,dfs2(to);
}
inline int LCA(int x,int y){
    while(top[x]!=top[y])if(dep[top[x]]>dep[top[y]])x=fa[top[x]];else y=fa[top[y]];
    return dep[x]<dep[y]?x:y;
}
void build(int l,int r,int o){
    if(l==r)_mn[o]=idfn[l];else{
        const int mid=(l+r)/2;
        build(l,mid,o<<1),build(mid+1,r,o<<1|1);
        _mn[o]=_min(_mn[o<<1],_mn[o<<1|1]);
    }
}
int query(int l,int r,int o,const int&L,const int&R){
    if(L<=l&&r<=R)return _mn[o];
    const int mid=(l+r)/2;
    if(L<=mid&&mid<R)return _min(query(l,mid,o<<1,L,R),query(mid+1,r,o<<1|1,L,R));
    if(L<=mid)return query(l,mid,o<<1,L,R);
    return query(mid+1,r,o<<1|1,L,R);
}
struct data{
    int u,x,y,d;
    inline bool operator<(const data&rhs)const{return d>rhs.d;}
};
priority_queue<data>q;
void update(int u,int x,int y,int s){
    if(dep[x]>=dep[y])return;
    int id=0;
    int v=y;
    while(top[v]!=top[x]){
        id=_min(id,mn[v]);
        v=fa[top[v]];
    }
    if(v!=x)id=_min(id,query(1,n,1,dfn[x]+1,dfn[v]));
    if(s+w[id]>1e8)return;
    q.push((data){id,x,y,s+w[id]});
}
inline void rebuild(){
    priority_queue<data>p;
    for(int i=0;i<k;++i)p.push(q.top()),q.pop();
    q=p;
}
void work(){
    for(int i=1;i<=n;++i)q.push((data){i,i,i,w[i]});
    for(int T=1;T<=k;++T){
        data dt=q.top();q.pop();
        int u=dt.u,d=dt.d;
        cout<<d<<'\n';
        update(u,dt.x,fa[u],d-w[u]);
        update(u,u,dt.y,d-w[u]);
        update(u,A[u][0].first,A[u][0].second,d);
        update(u,A[u][1].first,A[u][1].second,d);
        update(u,A[u][2].first,A[u][2].second,d);
        if((int)q.size()>3*k)rebuild();
    }
}
int main(){
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    cin>>n>>k;
    for(int i=1;i<=n;++i)cin>>w[i];w[0]=1e9;
    for(int i=2;i<=n;++i)cin>>fa[i],nxt[i]=head[fa[i]],head[fa[i]]=i;
    dfs(dep[1]=1),dfs2(mn[1]=top[1]=1);
    for(int i=1;i<=n;++i){
        int a,b,c;
        cin>>a>>b>>c;
        int ab=LCA(a,b),ac=LCA(a,c),bc=LCA(b,c);
        if(dep[ab]<=dep[ac]&&dep[ab]<=dep[bc]){
            A[i][0]=make_pair(ab,a),A[i][1]=make_pair(fa[ab],b);
            A[i][2]=make_pair((dep[ac]>dep[bc]?ac:bc),c);
        }else if(dep[ac]<=dep[ab]&&dep[ac]<=dep[bc]){
            A[i][0]=make_pair(ac,a),A[i][1]=make_pair(fa[ac],c);
            A[i][2]=make_pair((dep[ab]>dep[bc]?ab:bc),b);
        }else if(dep[bc]<=dep[ab]&&dep[bc]<=dep[ac]){
            A[i][0]=make_pair(bc,b),A[i][1]=make_pair(fa[bc],c);
            A[i][2]=make_pair((dep[ab]>dep[ac]?ab:ac),a);
        }else exit(1);
    }
    build(1,n,1);
    work();
    return 0;
}