【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个互不相同的正整数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),表示一个询问。
Output
输出q行。第i行输出第i个询问的答案,如果可行,则输出TAK,否则输出NIE。
Sample Input
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
Sample Output
TAK
TAK
TAK
NIE
题解:比较暴力的想法就是求出所有加油站之间的最短路,但显然复杂度太高,那么我们换一种思路,考虑每条边的贡献。
先跑多源最短路,求出对于每个点,离它最近的加油站是哪个,记为pre,以及最短路长度dis。然后枚举每条边<a,b>,如果pre[a]=pre[b],那么这条边显然没啥用。否则,我们在新图中连一条边<pre[a],pre[b]>,长度为dis[a]+dis[b]+val<a,b>。为什么可以这样做呢?因为假如我们从加油站c经过这条边想走到d,且c!=pre[a]&&c!=pre[b],那么dist(a,c)>dis[a],dist(b,c)>dis[b],我们可以先不走这条边,先去a和b加油,再回到这条边上,剩的油一定是不会比原来少的。
所以我们可以对于新图求最小生成树,并离线处理所有询问,将边从小到大扔到图中,用并查集判断两个点能否连通即可。
#include <cstdio> #include <cstring> #include <iostream> #include <algorithm> #include <queue> #include <utility> #define mp(A,B) make_pair(A,B) using namespace std; const int maxn=200010; int n,K,m,Q,cnt,tot,sum; int pos[maxn],to[maxn<<1],next[maxn<<1],val[maxn<<1],head[maxn],dis[maxn],pre[maxn],inq[maxn],vis[maxn]; int f[maxn],ans[maxn]; priority_queue<pair<int,int> > pq; struct node { int a,b,v,org; }p[maxn],q[maxn]; inline int rd() { int ret=0,f=1; char gc=getchar(); while(gc<‘0‘||gc>‘9‘) {if(gc==‘-‘) f=-f; gc=getchar();} while(gc>=‘0‘&&gc<=‘9‘) ret=ret*10+(gc^‘0‘),gc=getchar(); return ret*f; } bool cmp(const node &a,const node &b) { return a.v<b.v; } int find(int x) { return (f[x]==x)?x:(f[x]=find(f[x])); } inline void add(int a,int b,int c) { to[cnt]=b,val[cnt]=c,next[cnt]=head[a],head[a]=cnt++; } int main() { n=rd(),K=rd(),m=rd(); int i,j,u,a,b,c; memset(dis,0x3f,sizeof(dis)),memset(head,-1,sizeof(head)); for(i=1;i<=K;i++) a=pos[i]=rd(),dis[a]=0,pre[a]=i,pq.push(mp(0,a)); for(i=1;i<=m;i++) a=rd(),b=rd(),c=rd(),add(a,b,c),add(b,a,c); while(!pq.empty()) { u=pq.top().second,pq.pop(); if(vis[u]) continue; vis[u]=1; for(i=head[u];i!=-1;i=next[i]) if(dis[to[i]]>dis[u]+val[i]) dis[to[i]]=dis[u]+val[i],pre[to[i]]=pre[u],pq.push(mp(-dis[to[i]],to[i])); } for(i=1;i<=n;i++) for(j=head[i];j!=-1;j=next[j]) if(pre[i]<pre[to[j]]) p[++tot].a=pre[i],p[tot].b=pre[to[j]],p[tot].v=dis[i]+dis[to[j]]+val[j]; sort(p+1,p+tot+1,cmp); for(i=1;i<=n;i++) f[i]=i; Q=rd(); for(i=1;i<=Q;i++) q[i].a=pre[rd()],q[i].b=pre[rd()],q[i].v=rd(),q[i].org=i; sort(q+1,q+Q+1,cmp); for(i=j=1;i<=Q;i++) { for(;j<=tot&&p[j].v<=q[i].v;j++) { a=find(p[j].a),b=find(p[j].b); if(a!=b) f[a]=b; } if(find(q[i].a)==find(q[i].b)) ans[q[i].org]=1; } for(i=1;i<=Q;i++) { if(ans[i]) printf("TAK\n"); else printf("NIE\n"); } return 0; }//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