[NOI2018]归程 题解

[NOI2018]归程 题解

Problem

​ 给出一个有\(n\)个点,\(m\)条边的无向联通图,边有长度,海拔两个属性。给出\(Q\)个独立的询问,每次询问给出起点\(u\),水位线\(p\),当一条边的海拔不大于水位线时,这条边是有积水的。在\(u\)点时有一辆车,车不能通过有积水的边,你可以在任意节点下车,且下车后不能再次上车,问从\(u\)\(1\)最小的步行长度。多组数据,强制在线。

Solution

​ 容易发现,当水位线确定了,图会被分成若干个连通块,连通块里的点可以互相到达,故连通块内点的答案为其中的点到\(1\)的距离的最小值,这个可以先\(Dijkstra\)预处理求出(\(spfa\)死了)。

​ 那考虑怎么维护连通块。可持久化并查集?感觉不好搞,有一种神奇的算法——\(Kruscal\)重构树。

​ 其实就是\(Kruscal\)求最小生成树时,每一次合并两个点都建一个新点,新点的点权被赋值为两点间边的边权,并使其成为两个点新的父亲。这颗树有个优秀的性质——这是一个二叉堆。

​ 先按海拔从大到小把边排序,再建出\(Kruscal\)重构树,把新点的点权赋值为边的海拔。这样,从根到叶节点,点权,即海拔是单调递增的,并且每一个点的点权都小于其子树内的点权。

​ 有了这个,我们可以先dfs求出子树内叶子节点到\(1\)距离的最小值,之后从给定的\(u\)树上倍增跳至\(u\)的祖先\(v\),满足\(v\)的海拔大于给定的\(p\),点\(v\)子树内的所有叶子节点即是满足条件的连通块内的点。

Code

#include<bits/stdc++.h>
using namespace std;
int n,m,q,k,s,cnt,tot,last;
int head[400005],w[800005],to[800005],Next[800005];
int h[400005],fa[400005],vis[400005],dis[400005],Anc[400005][25];

struct Edge{

    int u,v,w,h;

    inline bool operator < (const Edge &x)const{
        return h>x.h;
    }

}E[400005];

struct Node{
    
    int u,dis;

    inline bool operator < (const Node &x)const{
        return dis>x.dis;
    }

};

inline int read(){
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){
       if(ch=='-')f=-1;
       ch=getchar();
    }
    while(ch>='0'&&ch<='9'){
       x=(x<<1)+(x<<3)+ch-'0';
       ch=getchar();
    }
    return x*f;
}

inline void add(int u,int v,int p){
    w[++cnt]=p;to[cnt]=v;Next[cnt]=head[u];head[u]=cnt;
}

inline int find(int x){
    return fa[x]==x?x:fa[x]=find(fa[x]);
}

void dfs(int u,int f){
    Anc[u][0]=f;
    for(register int i=1;i<=20;++i)
        Anc[u][i]=Anc[Anc[u][i-1]][i-1];
    for(register int i=head[u];i;i=Next[i]){
        int v=to[i];
        dfs(v,u);
        dis[u]=min(dis[u],dis[v]);
    }
    return;
}

int query(int u,int x){
    for(register int i=20;~i;--i){
        if(h[Anc[u][i]]>x)
            u=Anc[u][i];
    }
    return dis[u];
}

void Dijkstra(){
    priority_queue<Node>q;
    memset(vis,0   ,sizeof vis);
    memset(dis,0x3f,sizeof dis);
    q.push((Node){1,0});dis[1]=0;
    while(!q.empty()){
        Node u=q.top();q.pop();
        if(vis[u.u])
            continue;
        vis[u.u]=1;
        for(register int i=head[u.u];i;i=Next[i]){
            int v=to[i];
            if(dis[v]>dis[u.u]+w[i]){
                dis[v]=dis[u.u]+w[i];
                if(!vis[v])
                    q.push((Node){v,dis[v]});
            }
        }
    }
    return;
}

void Kruscal(){

    tot=n;cnt=0;
    memset(head,0,sizeof head);

    sort(E+1,E+m+1);

    for(register int i=1;i<=n;++i)
        fa[i]=i,fa[i+n]=i+n;
    
    for(register int i=1;i<=m;++i){
        int u=E[i].u,v=E[i].v;
        if(find(u)!=find(v)){
            int fu=find(u),fv=find(v);
            fa[fu]=fa[fv]=++tot;
            h[tot]=E[i].h;
            add(tot,fu,0);
            add(tot,fv,0);
        }
        if(tot==n+n-1)
            break;
    }

    dfs(tot,0);

    return;
}

void work(){

    memset(head,0,sizeof head);

    cnt=0;n=read();m=read();

    for(register int i=1;i<=m;++i){
        E[i]=(Edge){read(),read(),read(),read()};
        add(E[i].u,E[i].v,E[i].w);
        add(E[i].v,E[i].u,E[i].w);
    }

    Dijkstra();Kruscal();

    last=0;q=read();k=read();s=read();

    for(register int i=1;i<=q;++i){
        int u=(read()+k*last-1)%n+1;
        int p=(read()+k*last)%(s+1);
        printf("%d\n",last=query(u,p));
    }

    return;
}

int main(){

    int T=read();
    
    while(T--)
        work();
    
    return 0;
}
posted @ 2020-09-22 20:46  zjy123456  阅读(123)  评论(0编辑  收藏  举报