Luogu P4768 [NOI2018] 归程

蒟蒻的第一篇题解QAQ 不太会写,人菜瘾大
第一次写Kruskal重构树,边WA边Debug

题目传送门

P4768 [NOI2018] 归程

题意

对于一张有n个节点,m条边的图,每条边有其相对应的长度与海拔。

每天的出发点有一辆车,可以开过高于当天水位线的边,对于海拔低于或等于当天水位线的边,只能步行通过,并且车无法通过,这意味着经过此边后的所有边都只能步行通过。

每次询问对于当天水位线为p,从v点出发去往1号点的最小步行距离。

思路

我们可以把这张图大致分为两部分,一部分是可以开车到达的点,这些点不需要步行;另一部分就是需要步行的路了。

我们的目标是需要步行的路最短,也就是要把可以开车到达的那一部分点给找出来,那部分点中距离1号点最近的点即为答案了。

对于每个点距离1号点的距离,我们可以一次Dijkstra求出。

而对于哪些点可以由汽车到达,也就是哪些点可以由都在当天水位线上的边到达的,我们可以用Kruskal重构树求得。

首先我们把每条边按照海拔由高到低排序,然后跑Kruskal重构树,此时构建的树去求两点lca即为两点间所有可行的路里最低的边的最大值。

对于每次询问,如果出发点在重构树中的父节点权值小于等于水位线,则意味着出发点无法驱车前往任意点,直接返回出发点到1节点的步行距离即可。

否则,找到重构树中深度最小且海拔大于水位线的节点,依据重构树是小根堆的性质,该节点的子树内的所有节点海拔都比该节点大,即返回该节点及其子树内所有节点到达1号节点的最小距离。对于找节点的操作,可以用树上倍增解决。对于找子树中到1号点最小距离的操作,可以用dfs预处理一遍。

代码

Dijkstra求最短路

class dij{
public:
    long long d;
    int u;
    bool operator > (const dij& a)const{return d>a.d;}
};
void dijkstra()
{
    d[1]=0;
    priority_queue<dij,vector<dij>,greater<dij> > Q;
    Q.push({0, 1});
    while(!Q.empty())
    {
        int now=Q.top().u;
        Q.pop();
        if(vis[now])continue;
        vis[now]=1;
        for(int i=head_e[now]; i; i=E[i].next)
        {
            int v=E[i].v;
            int w=E[i].w;
            if(d[v]>d[now]+w)
            {
                d[v]=d[now]+w;
                Q.push({d[v], v});
            }
        }
    }
}

Kruskal重构树

void kruskal()
{
    int tmp=0;
    for(int i=1;i<=m;i++)
    {
        int x=V[i].u,y=V[i].v,h=V[i].h;
        int tx=get(x),ty=get(y);//并查集操作
        if(tx==ty)continue;
        tmp++;
        int mubiao=++tree_p;
        tree_w[mubiao]=h;
        add_oe(tx,mubiao);//第一次写kruskal重构树,一堆加边操作
        add_oe(mubiao,tx);
        add_oe(ty,mubiao);
        add_oe(mubiao,ty);
        fa[tx]=mubiao;//并查集合并
        fa[ty]=mubiao;
        if(!rely[x])rely[x]=mubiao;//记录重构树中其父节点
        if(!rely[y])rely[y]=mubiao;
        if(tmp==n-1)break;
    }
}

倍增预处理

void dfs_bz(int u,int from)
{
    faa[u][0]=from;//无倍增时祖先为其父节点
    deepth[u]=deepth[from]+1;//深度+1
    for(int i=1;i<=lg[deepth[u]];i++)
    {
        faa[u][i]=faa[faa[u][i-1]][i-1];//对其父节点的倍增预处理
    }
    for(int i=head_oe[u]; i; i=OE[i].next)
    {
        int to=OE[i].v;
        if(to<=n)continue;
        if(to==from)continue;
        dfs_bz(to,u);
    }
}

dfs预处理答案

void dfs_ans(int u,int from)
{
    for(int i=head_oe[u]; i; i=OE[i].next)
    {
        int v=OE[i].v;
        if(v==from)continue;
        if(v<=n)
        {
            ans[u]=min(ans[u],d[v]);//如果是实点,则更新本虚点的权值
        }
        else
        {
            dfs_ans(v,u);//如果是虚点,则先继续深搜
            ans[u]=min(ans[u],ans[v]);//然后用其更新本点权值
        }
    }
}

寻找深度最浅且海拔大于P的祖先节点

int find_u()
{
    int tmp=rely[st];//tmp为实点在重构树中的父虚点
    while(!(tree_w[faa[tmp][0]]<=P&&tree_w[tmp]>P))//如果本节点不是深度最小的,且海拔大于P的节点
    {
        for(int i=lg[deepth[tmp]-1];i>=0;i--)
        {
            if(tree_w[faa[tmp][i]]>P)//倍增寻找海拔大于P的祖先节点
            {
                tmp=faa[tmp][i];
                break;
            }
        }
        if(tmp==tree_p)break;//如果已经是根节点,则break
    }
    return tmp;
}

完整代码

点击查看代码
/*
 * ycccc319
 */
#include <bits/stdc++.h>
const int MAXN=400010;
using namespace std;
int n,m,q,K,S,st,P;
class node{
public:
    int u,v,h;
}V[MAXN];//kruskal最小生成树用
class edge{
public:
    int v,w,next;
}E[MAXN<<1];//最短路用
class oedge{
public:
    int v,next;
}OE[MAXN<<1];//kruskal重建树边
class dij{
public:
    long long d;
    int u;
    bool operator > (const dij& a)const{return d>a.d;}
};
long long d[MAXN],lastans=0,ans[MAXN];
int head_e[MAXN],head_oe[MAXN],cnt_e,cnt_v,cnt_oe,fa[MAXN],rely[MAXN];
int tree_p,tree_w[MAXN];
int deepth[MAXN],faa[MAXN][24],lg[MAXN];
bool vis[MAXN];
bool cmp_v(node a,node b)
{
    return a.h>b.h;
}
void init()//初始化
{
    memset(head_e, 0, sizeof(int) * (n + 1));
    memset(head_oe, 0, sizeof(int) * (n << 1));
    memset(d,0x3f,sizeof(long long)*(n+1));
    memset(vis,0,sizeof(bool)*(n+1));
    memset(tree_w,0,sizeof(int)*(n<<1));
    memset(rely,0,sizeof(int)*(n+1));
    memset(ans,0x3f,sizeof(long long)*(n<<1));
    cnt_e= cnt_v= cnt_oe=0;
    lastans=0;
    tree_p=n;
    for(int i=1;i<=(n<<1);i++)fa[i]=i;
}
inline int get(int x)//并查集找父节点
{
    if(fa[x]==x)return x;
    else return fa[x]=get(fa[x]);
}
void add_e(int a,int b,int w)//最短路用加边
{
    E[++cnt_e].v=b;
    E[cnt_e].w=w;
    E[cnt_e].next=head_e[a];
    head_e[a]=cnt_e;
}
void add_v(int a,int b,int h)//生成树用加边
{
    V[++cnt_v].u=a;
    V[cnt_v].v=b;
    V[cnt_v].h=h;
}
void add_oe(int a,int b)//重构树用加边
{
    OE[++cnt_oe].v=b;
    OE[cnt_oe].next=head_oe[a];
    head_oe[a]=cnt_oe;
}
void dijkstra()
{
    d[1]=0;
    priority_queue<dij,vector<dij>,greater<dij> > Q;
    Q.push({0, 1});
    while(!Q.empty())
    {
        int now=Q.top().u;
        Q.pop();
        if(vis[now])continue;
        vis[now]=1;
        for(int i=head_e[now]; i; i=E[i].next)
        {
            int v=E[i].v;
            int w=E[i].w;
            if(d[v]>d[now]+w)
            {
                d[v]=d[now]+w;
                Q.push({d[v], v});
            }
        }
    }
}
void kruskal()
{
    int tmp=0;
    for(int i=1;i<=m;i++)
    {
        int x=V[i].u,y=V[i].v,h=V[i].h;
        int tx=get(x),ty=get(y);//并查集操作
        if(tx==ty)continue;
        tmp++;
        int mubiao=++tree_p;
        tree_w[mubiao]=h;
        add_oe(tx,mubiao);//第一次写kruskal重构树,一堆加边操作
        add_oe(mubiao,tx);
        add_oe(ty,mubiao);
        add_oe(mubiao,ty);
        fa[tx]=mubiao;//并查集合并
        fa[ty]=mubiao;
        if(!rely[x])rely[x]=mubiao;//记录重构树中其父节点
        if(!rely[y])rely[y]=mubiao;
        if(tmp==n-1)break;
    }
}
void dfs_bz(int u,int from)
{
    faa[u][0]=from;//无倍增时祖先为其父节点
    deepth[u]=deepth[from]+1;//深度+1
    for(int i=1;i<=lg[deepth[u]];i++)
    {
        faa[u][i]=faa[faa[u][i-1]][i-1];//对其父节点的倍增预处理
    }
    for(int i=head_oe[u]; i; i=OE[i].next)
    {
        int to=OE[i].v;
        if(to<=n)continue;
        if(to==from)continue;
        dfs_bz(to,u);
    }
}
int find_u()
{
    int tmp=rely[st];//tmp为实点在重构树中的父虚点
    while(!(tree_w[faa[tmp][0]]<=P&&tree_w[tmp]>P))//如果本节点不是深度最小的,且海拔大于P的节点
    {
        for(int i=lg[deepth[tmp]-1];i>=0;i--)
        {
            if(tree_w[faa[tmp][i]]>P)//倍增寻找海拔大于P的祖先节点
            {
                tmp=faa[tmp][i];
                break;
            }
        }
        if(tmp==tree_p)break;//如果已经是根节点,则break
    }
    return tmp;
}
void dfs_ans(int u,int from)
{
    for(int i=head_oe[u]; i; i=OE[i].next)
    {
        int v=OE[i].v;
        if(v==from)continue;
        if(v<=n)
        {
            ans[u]=min(ans[u],d[v]);//如果是实点,则更新本虚点的权值
        }
        else
        {
            dfs_ans(v,u);//如果是虚点,则先继续深搜
            ans[u]=min(ans[u],ans[v]);//然后用其更新本点权值
        }
    }
}
int main() {
    for(int i=1;i<MAXN;++i){
        lg[i]=lg[i-1]+(1<<lg[i-1]==i);
    }
    int t;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d",&n);
        scanf("%d",&m);
        init();
        for(int i=1;i<=m;i++)
        {
            int u,v,w,h;
            scanf("%d",&u);
            scanf("%d",&v);
            scanf("%d",&w);
            scanf("%d",&h);
            add_e(u,v,w);
            add_e(v,u,w);
            add_v(u,v,h);
        }
        dijkstra();
        sort(V+1,V+m+1,cmp_v);
        kruskal();
        scanf("%d",&q);
        scanf("%d",&K);
        scanf("%d",&S);
        deepth[0]=0;
        dfs_bz(tree_p,0);
        dfs_ans(tree_p,0);
        while(q--)
        {
            int v0,p0;
            scanf("%d",&v0);
            scanf("%d",&p0);
            st=(v0+K*lastans-1)%n+1;
            P=(p0+K*lastans)%(S+1);
            if(P==0)//水位线为0特判
            {
                printf("0\n");
                lastans=0;
                continue;
            }
            if(tree_w[rely[st]]<=P)//出发点不能坐车去任意点特判
            {
                printf("%lld\n",d[st]);
                lastans=d[st];
                continue;
            }
            int u=find_u();
            printf("%lld\n",ans[u]);
            lastans=ans[u];
        }
    }
    return 0;
}
posted @   ycccc319  阅读(51)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
主题色彩
点击右上角即可分享
微信分享提示