[AMPPZ2014] Petrol

问题描述

给定一个n个点、m条边的带权无向图,其中有s个点是加油站。

每辆车都有一个油量上限b,即每次行走距离不能超过b,但在加油站可以补满。

q次询问,每次给出x,y,b,表示出发点是x,终点是y,油量上限为b,且保证x点和y点都是加油站,请回答能否从x走到y。

输入格式

第一行包含三个正整数n,s,m(2<=s<=n<=200000,1<=m<=200000),表示点数、加油站数和边数。

第二行包含s个互不相同的正整数c[1],c[2],...c[s] (1<=c[i]<=n),表示每个加油站。

接下来m行,每行三个正整数u[i],v[i],d[i] (1<=u[i],v[i]<=n,u[i]!=v[i],1<=d[i]<=10000),表示u[i]和v[i]之间有一条长度为d[i]的双向边。

接下来一行包含一个正整数q(1<=q<=200000),表示询问数。

接下来q行,每行包含三个正整数x[i],y[i],b[i] (1<=x[i],y[i]<=n,x[i]!=y[i],1<=b[i]<=2*10^9),表示一个询问。

输出格式

输出q行。第i行输出第i个询问的答案,如果可行,则输出TAK,否则输出NIE。

样例输入

6 4 5
1 5 2 6
1 3 1
2 3 2
3 4 3
4 5 5
6 4 5
4
1 2 4
2 6 9
1 5 9
6 5 8

样例输出

TAK
TAK
TAK
NIE

解析

如果全是加油站,问题就变成了对每一个询问,查找是否存在一条S到T的路径使该路径上的每一条边的长度均小于b。我们可以利用最小生成树中两点路径上的最大边最小的性质,对原图求最小生成树,利用倍增查询两点之间边权最大值,如果小于等于b就说明可行。

但问题是还有其他的点。我们需要将原图改为只有加油站构成的图。可以发现,改编图中两点之间的一条边对应原图中两点的一条路径。设\(p[i]\)表示离i最近的加油站,\(dis[i]\)表示到最近的加油站的距离。观察一条的路径,一定会存在一条边使得该边的两端点的p值不同。也就是说,在求出\(p\)和\(dis\)后,对于每一条边\((u,v)\),如果\(p[u]\)不等于\(p[v]\),说明\(p[u]\)到\(p[v]\)之间有一条路径,长度为\(dis[u]+dis[v]+len(u,v)\)。对应地在新图中连边即可。

还有几个需要注意的地方。

1.这样做会导致重边很多,要注意去重。

2.新图可能是不连通的,MST会是一个最小生成树森林。所以在倍增查询之前要判断两点是否在同一个连通块中。

代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#include <cmath>
#define N 200002
#define M 200002
using namespace std;
struct Edge{
    int u,v,w;
}e[M];
int head[N],ver[M*2],nxt[M*2],edge[M*2],l;
int n,s,m,q,i,j,c[N],p[N],dis[N],cnt,fa[N],dep[N],f[N][30],g[N][30],num,bcc[N];
bool in[N];
int read()
{
    char c=getchar();
    int w=0;
    while(c<'0'||c>'9') c=getchar();
    while(c<='9'&&c>='0'){
        w=w*10+c-'0';
        c=getchar();
    }
    return w;
}
void insert(int x,int y,int z)
{
    l++;
    ver[l]=y;
    edge[l]=z;
    nxt[l]=head[x];
    head[x]=l;
}
void SPFA()
{
    queue<int> q;
    memset(dis,0x3f,sizeof(dis));
    for(int i=1;i<=s;i++){
        q.push(c[i]);
        dis[c[i]]=0;in[c[i]]=1;
        p[c[i]]=c[i];
    }
    while(!q.empty()){
        int x=q.front();
        q.pop();
        for(int i=head[x];i;i=nxt[i]){
            int y=ver[i];
            if(dis[y]>dis[x]+edge[i]){
                dis[y]=dis[x]+edge[i];
                p[y]=p[x];
                if(!in[y]){
                    in[y]=1;
                    q.push(y);
                }
            }
        }
        in[x]=0;
    }
}
int my_comp(const Edge &x,const Edge &y)
{
    return x.w<y.w;
}
int find(int x)
{
    if(fa[x]!=x) fa[x]=find(fa[x]);
    return fa[x];
}
void Kruskal()
{
    memset(head,0,sizeof(head));
    l=0;
    int num=n;
    for(int i=1;i<=n;i++) fa[i]=i;
    sort(e+1,e+cnt+1,my_comp);
    for(int i=1;i<=cnt;i++){
        if(num==1) break;
        int f1=find(e[i].u),f2=find(e[i].v);
        if(f1!=f2){
            num--;
            fa[f1]=f2;
            insert(e[i].u,e[i].v,e[i].w);
            insert(e[i].v,e[i].u,e[i].w);
        }
    }
}
void dfs(int x,int pre)
{
    bcc[x]=num;
    dep[x]=dep[pre]+1;
    f[x][0]=pre;
    for(int i=head[x];i;i=nxt[i]){
        int y=ver[i];
        if(y!=pre){
            g[y][0]=edge[i];
            dfs(y,x);
        }
    }
}
void init()
{
    for(int i=1;i<=s;i++){
        if(!dep[c[i]]){
            num++;
            dfs(c[i],0);
        }
    }
    for(int j=0;(1<<(j+1))<s;j++){
        for(int i=1;i<=s;i++){
            if(f[c[i]][j]==0) f[c[i]][j+1]=0;
            else f[c[i]][j+1]=f[f[c[i]][j]][j];
            g[c[i]][j+1]=max(g[c[i]][j],g[f[c[i]][j]][j]);
        }
    }
}
int ask(int u,int v)
{
    if(dep[u]>dep[v]) swap(u,v);
    int tmp=dep[v]-dep[u],ans=0;
    for(int i=0;(1<<i)<=tmp;i++){
        if(tmp&(1<<i)){
            ans=max(ans,g[v][i]);
            v=f[v][i];
        }
    }
    if(u==v) return ans;
    for(int i=log2(1.0*s);i>=0;i--){
        if(f[u][i]!=f[v][i]){
            ans=max(ans,max(g[u][i],g[v][i]));
            u=f[u][i],v=f[v][i];
        }
    }
    ans=max(ans,max(g[u][0],g[v][0]));
    return ans;
}
int main()
{
    n=read();s=read();m=read();
    for(i=1;i<=s;i++) c[i]=read();
    for(i=1;i<=m;i++){
        int u=read(),v=read(),w=read();
        insert(u,v,w);
        insert(v,u,w);
    }
    SPFA();
    for(i=1;i<=n;i++){
        for(j=head[i];j;j=nxt[j]){
            if(p[i]<p[ver[j]]) e[++cnt]=(Edge){p[i],p[ver[j]],dis[i]+dis[ver[j]]+edge[j]};
        }
    }
    Kruskal();
    init();
    q=read();
    for(i=1;i<=q;i++){
        int x=read(),y=read(),b=read();
        if(bcc[x]!=bcc[y]||ask(x,y)>b) puts("NIE");
        else puts("TAK");
    }
    return 0;
}

总结

  • 这题提供了一个关于将原图转换为只有关键点的图的解法,需要积累。
  • 做题时可以将想到的东西写下来,将这些碎片式的思路组合成一个解法。

原文地址:https://www.cnblogs.com/LSlzf/p/11671002.html

时间: 2024-08-30 13:37:10

[AMPPZ2014] Petrol的相关文章

BZOJ 4144: [AMPPZ2014]Petrol

4144: [AMPPZ2014]Petrol Time Limit: 10 Sec  Memory Limit: 256 MBSubmit: 457  Solved: 170[Submit][Status][Discuss] Description 给定一个n个点.m条边的带权无向图,其中有s个点是加油站. 每辆车都有一个油量上限b,即每次行走距离不能超过b,但在加油站可以补满. q次询问,每次给出x,y,b,表示出发点是x,终点是y,油量上限为b,且保证x点和y点都是加油站,请回答能否从x走

bzoj4144 [AMPPZ2014]Petrol 图论 最短路 并查集

bzoj4144 [AMPPZ2014]Petrol 图论 最短路 并查集 1.这道题我们主要就是要求出距离一个油站的最近的油站 首先我们dijkstra 求出任意一个点到 离他最近的油站的距离 2.然后会发现 如果一条边的两个端点 的最近油站不同的话 那么这条边就会在这两个油站的最短路上 3.然后对于每一条边 我们将他的权值 变为 dis[ u ] + dis[ v ] + e[ i ][ j ] 如果u与v最近油站相同 那么这个无意义 如果不同 那么久表示 u 最近油站 到 v 最近油站的最

【BZOJ4144】[AMPPZ2014]Petrol 最短路+离线+最小生成树

[BZOJ4144][AMPPZ2014]Petrol Description 给定一个n个点.m条边的带权无向图,其中有s个点是加油站. 每辆车都有一个油量上限b,即每次行走距离不能超过b,但在加油站可以补满. q次询问,每次给出x,y,b,表示出发点是x,终点是y,油量上限为b,且保证x点和y点都是加油站,请回答能否从x走到y. Input 第一行包含三个正整数n,s,m(2<=s<=n<=200000,1<=m<=200000),表示点数.加油站数和边数. 第二行包含s

4144: [AMPPZ2014]Petrol (多源最短路+最小生成树+启发式合并)

4144: [AMPPZ2014]Petrol Time Limit: 10 Sec  Memory Limit: 256 MBSubmit: 752  Solved: 298[Submit][Status][Discuss] Description 给定一个n个点.m条边的带权无向图,其中有s个点是加油站. 每辆车都有一个油量上限b,即每次行走距离不能超过b,但在加油站可以补满. q次询问,每次给出x,y,b,表示出发点是x,终点是y,油量上限为b,且保证x点和y点都是加油站,请回答能否从x走

bzoj4144 [AMPPZ2014]Petrol

Description 给定一个 \(n\)个 点. \(m\) 条边的带权无向图,其中有 \(s\) 个点是加油站. 每辆车都有一个油量上限 \(b\) ,即每次行走距离不能超过 \(b\) ,但在加油站可以补满. \(q\) 次询问,每次给出 \(x,y,b\) ,表示出发点是 \(x\) ,终点是 \(y\) ,油量上限为 \(b\) ,且保证 \(x\) 点和 \(y\) 点都是加油站,请回答能否从 \(x\) 走到 \(y\) . Input 第一行包含三个正整数 \(n,s,m(2\

【BZOJ】【4144】【AMPPZ2014】Petrol

最短路+最小生成树+倍增 图论问题中综合性较强的一题= =(Orz vfk) 比较容易发现,关键的还是有加油站的这些点,其他点都是打酱油的. 也就是说我们重点是要求出 关键点之间的最短路. 这玩意……如果枚举加油站所在的点,然后跑单源最短路什么的……肯定TLE啊. 我们记from[i]表示离 i 最近的关键点,仔细考虑一下,A->B的最短路径上,一定是前一半的from[i]为A,然后走过某条路以后,后一半的from[i]为B.为什么呢?我们不妨设中间出现了一个点x的from[x]=C,那么我们大

AMPPZ2014

[AMPPZ2014]The Lawyer 记录每天结束的最早的会议以及开始的最晚的会议即可. #include<cstdio> #define N 500010 int n,m,i,d,a[N],b[N],st[21],en[21]; inline void read(int&a){char c;while(!(((c=getchar())>='0')&&(c<='9')));a=c-'0';while(((c=getchar())>='0')&am

「AMPPZ2014」Petrol

传送门: 这是一道bzoj权限题 Luogu团队题链接 解题思路 首先对于每一个点 \(x\) 预处理出 \(nr[x]\) 和 \(dis[x]\),分别表示离 \(x\) 最近的加油站以及该段距离. 这个过程可以用多源 \(\text{Dijkstra}\) 处理. 然后对于每一条原图中的边 \((u, v, w)\)(\(nr[u]\ne nr[v]\)) 改为这样一条边:\((nr[u], nr[v], dis[u] + dis[v] + w)\) 然后只要离线用并查集维护一下连通性即可

【BZOJ 4148】 4148: [AMPPZ2014]Pillars (乱搞)

4148: [AMPPZ2014]Pillars Time Limit: 5 Sec  Memory Limit: 256 MBSec  Special JudgeSubmit: 100  Solved: 49 Description 给定一个n*m的矩形,其中有f个2*2的障碍物,其中任意两个障碍物中心之间的欧几里得距离至少为6, 且每个障碍物的中心到边缘的距离至少为3.请找到一条从左下角(1,1)出发经过所有没有障碍物的点各 一次的且最后回到左下角的回路. Input 第一行包含三个整数n,