【BZOJ】4144: [AMPPZ2014]Petrol

题意

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

分析

首先来分析如果只有一个询问,给出\(x\)点和\(y\)点问\(x\)是否能走到\(y\)。假设经过的非加油站的点集依次是\(p_1, p_2, \cdots, p_k\),那么考虑从\(p_1\)走到\(p_2\),假设有边\((p_1, p_2)\)(否则一定是从加油站走到\(p_2\)的),权值为\(w\)。如果我们从\(p_1\)走到最近的加油站(距离为\(d(p_1)\))加满油再回来,还剩的油为\(b-d(p_1)\),然后再走这条边。而如果直接从\(p_1\)走到\(p_2\),由于没有加油,此时在\(p_1\)的油量显然是\(\le b-d(p_1)\)的。显然加了油更优。

题解

根据分析,我们只需要按新赋值的边权w+d(u)+d(v)生成最小生成树来判断即可。对于询问,离线一下即可。

#include <bits/stdc++.h>
using namespace std;
const int N=200005, M=200005;
int ihead[N], cnt, n, m, s, p[N], h[N], c[N];
struct E {
    int next, to, w;
}e[M<<1];
struct ED {
    int x, y, d;
}ed[M];
struct QU {
    int x, y, b, id;
}q[N];
void add(int x, int y, int w) {
    e[++cnt]=(E){ihead[x], y, w}; ihead[x]=cnt;
    e[++cnt]=(E){ihead[y], x, w}; ihead[y]=cnt;
}
int find(int x) {
    return x==p[x]?x:p[x]=find(p[x]);
}
typedef pair<int, int> pii;
#define mkpii make_pair<int, int>
priority_queue<pii, vector<pii>, greater<pii> > qu;
int d[N], vis[N];
void dij() {
    memset(d, 0x7f, sizeof(int)*(n+1));
    for(int i=0; i<s; ++i) {
        d[c[i]]=0;
        qu.push(mkpii(0, c[i]));
    }
    while(qu.size()) {
        int x=qu.top().second;
        qu.pop();
        if(vis[x]) {
            continue;
        }
        vis[x]=1;
        for(int i=ihead[x]; i; i=e[i].next) {
            int y=e[i].to;
            if(d[y]>d[x]+e[i].w) {
                d[y]=d[x]+e[i].w;
                qu.push(mkpii(d[y], y));
            }
        }
    }
}
inline bool cmp1(const ED &a, const ED &b) {
    return a.d<b.d;
}
inline bool cmp2(const QU &a, const QU &b) {
    return a.b<b.b;
}
int ans[N];
int main() {
    scanf("%d%d%d", &n, &s, &m);
    for(int i=1; i<=n; ++i) {
        p[i]=i;
        h[i]=1;
    }
    for(int i=0; i<s; ++i) {
        scanf("%d", &c[i]);
    }
    for(int i=0; i<m; ++i) {
        scanf("%d%d%d", &ed[i].x, &ed[i].y, &ed[i].d);
        add(ed[i].x, ed[i].y, ed[i].d);
    }
    dij();
    for(int i=0; i<m; ++i) {
        ed[i].d+=d[ed[i].x]+d[ed[i].y];
    }
    int Q;
    scanf("%d", &Q);
    for(int i=0; i<Q; ++i) {
        scanf("%d%d%d", &q[i].x, &q[i].y, &q[i].b);
        q[i].id=i;
    }
    sort(ed, ed+m, cmp1);
    sort(q, q+Q, cmp2);
    int now=0;
    for(int i=0; i<Q; ++i) {
        while(now<m && ed[now].d<=q[i].b) {
            int fx=find(ed[now].x), fy=find(ed[now].y);
            if(fx!=fy) {
                if(h[fx]>h[fy]) {
                    swap(fx, fy);
                }
                p[fx]=fy;
                h[fy]+=h[fy]==h[fx];
            }
            ++now;
        }
        ans[q[i].id]=find(q[i].x)==find(q[i].y);
    }
    for(int i=0; i<Q; ++i) {
        puts(ans[i]?"TAK":"NIE");
    }
    return 0;
}

转载于:https://www.cnblogs.com/iwtwiioi/p/4986381.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值