洛谷P4768 kruskal重构树

题意:

n n n个城市, m m m条连接两个城市的道路,每条道路有一个海拔 h i h_{i} hi和长度 l i l_{i} li。目前这些城市正发洪水,小明家在节点1,他现在需要回家。他有一辆自行车,但这个自行车只能在没有被水淹的道路骑行。对于每条道路,如果道路的海拔不高于当日的水位线,那么这条路就会被淹。小明如果需要经过一条被淹的道路,则需要丢弃他的自行车,然后徒步过去,自行车如果被丢弃了就不能被找回来了。有 q q q次询问,每次询问给出小明目前所在的位置 x x x和当日水位线 h h h,问他回家最少需要步行多长距离?可以认为小明无论骑车还是步行都是无限快的,也就是他回到家之前水位线都是不变的。

Solution:

显然有一些边是不能经过的,并且海拔越低的边越不能经过,所以考虑 K r u s k a l Kruskal Kruskal重构树,由路的海拔为关键词从大到小加入重构树,那么对于目前所在的节点 x x x和当日海拔 h h h,我们找到一个 u u u,满足

v a l [ u ] > h , v a l [ f a [ u ] ] < = h val[u]>h,val[fa[u]]<=h val[u]>h,val[fa[u]]<=h

那么这颗以 u u u为根的子树的所有点都是我们能够到达的,现在问题是,我们该从哪个我们能到达的节点开始步行回去呢?我们在子树内找到任意一个点,到1的最短路可以计算出来,但又如何减去骑车的部分呢?不妨考虑这样的情况,有一个点 A A A,和一副

连通图 G G G A A A不和图联通,定义两点距离为实际距离-在 G G G图中经过的边权总和,也就是 G G G图不需要花费,随便跑,那么到这个集合的某一点距离的最小值就应该是到这个图的点的实际距离的最小值。所以原问题就变成了 u u u这颗子树的到1的最短路的最小值是多少,预处理源点为1的最短路,定义 m i n 1 [ i ] min1[i] min1[i] i i i这棵子树的最小最短路距离,那么答案就是 m i n 1 [ u ] min1[u] min1[u]了,找这个点倍增即可

// #include<bits/stdc++.h>
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<numeric>
using namespace std;

using ll=long long;
const int N=400005,inf=0x3fffffff;
const long long INF=0x3f3f3f3f3f3f,mod=998244353;

struct wway
{
    int u,v,l,h;
}eedge[N];

struct graph
{
    struct way
    {
        int to,next,w;
    }edge[N<<2];
    int cnt,head[N<<1];
    void add(int u,int v,int w=0)
    {
        edge[++cnt].to=v;
        edge[cnt].next=head[u];
        edge[cnt].w=w;
        head[u]=cnt;
    }
}A,B;

int f[N<<1];

int find(int k)
{
    if(f[k]==k) return k;
    return f[k]=find(f[k]);
}

bool vis[N];
ll dis[N];
int n,m,tot,val[N<<1],fa[N<<1][21],min1[N<<1];
priority_queue<pair<ll,int>,vector<pair<ll,int>>,greater<pair<ll,int>>>q;

void KruskalRebuild()
{
    sort(eedge+1,eedge+1+m,[&](const wway& x,const wway& y){
        return x.h>y.h;
    }); 
    tot=n; A.cnt=0;
    for(int i=1;i<=n+m;i++)
    {
        f[i]=i;
        A.head[i]=0;
        val[i]=0;
    }
    for(int i=1;i<=m;i++)
    {
        auto& [u,v,l,h]=eedge[i];
        int pa=find(u),pb=find(v);
        if(pa==pb) continue;
        f[pa]=f[pb]=fa[pa][0]=fa[pb][0]=++tot;
        val[tot]=h;
        A.add(pa,tot); A.add(tot,pa);
        A.add(pb,tot); A.add(tot,pb);
    }
}

void dijkstra()
{
    for(int i=1;i<=n;i++)
    {
        dis[i]=numeric_limits<int>::max();
        vis[i]=false;
    }
    q.push({dis[1]=0,1});
    while(!q.empty())
    {
        int u=q.top().second; q.pop();
        if(vis[u]) continue;
        vis[u]=true;
        for(int i=B.head[u];i;i=B.edge[i].next)
        {
            int v=B.edge[i].to,w=B.edge[i].w;
            if(dis[u]+w<dis[v]) q.push({dis[v]=dis[u]+w,v});
        }
    }
}
//A图是重构树,B图是原图
void dfs(int u,int fa)
{
    if(u<=n) min1[u]=dis[u];
    else min1[u]=numeric_limits<int>::max();
    for(int i=1;i<=20;i++) ::fa[u][i]=::fa[::fa[u][i-1]][i-1];
    for(int i=A.head[u];i;i=A.edge[i].next)
    {
        int v=A.edge[i].to;
        if(v==fa) continue;
        dfs(v,u);
        min1[u]=min(min1[u],min1[v]);
    }
}

ll solve(int x,int k)
{
    for(int i=20;i>=0;i--)
        if(fa[x][i]&&val[fa[x][i]]>k) x=fa[x][i];
    return min1[x];
}

void work()
{
    cin>>n>>m; B.cnt=0;
    for(int i=1;i<=n;i++)
    {
        dis[i]=numeric_limits<int>::max();
        B.head[i]=0;
    }
    for(int i=1;i<=m;i++)
    {
        auto& [u,v,l,h]=eedge[i];
        scanf("%d%d%d%d",&u,&v,&l,&h);
        B.add(u,v,l); B.add(v,u,l);
    }
    dijkstra();
    KruskalRebuild();
    dfs(tot,0);
    int q,K,S; ll lastans=0; cin>>q>>K>>S;
    while(q--)
    {
        int x,h; scanf("%d%d",&x,&h);
        x=((x+K*lastans)-1)%n+1;
        h=(h+K*lastans)%(S+1);
        printf("%lld\n",lastans=solve(x,h));
    }
}

int main()
{
    // freopen("in.txt","r",stdin);
    int T; cin>>T;
    while(T--) work();
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值