【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个互不相同的正整数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
上一篇:java===java基础学习(2)---运算符,三元操作符,数学函数


下一篇:java笔记 -- 数学函数与常量