题目描述
Farmer John正在一个新的销售区域对他的牛奶销售方案进行调查。他想把牛奶送到T个城镇 (1 <= T <= 25,000),编号为1T。这些城镇之间通过R条道路 (1 <= R <= 50,000,编号为1到R) 和P条航线 (1 <= P <= 50,000,编号为1到P) 连接。每条道路i或者航线i连接城镇A_i (1 <= A_i <= T)到B_i (1 <= B_i <= T),花费为C_i。对于道路,0 <= C_i <= 10,000;然而航线的花费很神奇,花费C_i可能是负数(-10,000 <= C_i <= 10,000)。道路是双向的,可以从A_i到B_i,也可以从B_i到A_i,花费都是C_i。然而航线与之不同,只可以从A_i到B_i。事实上,由于最近恐怖主义太嚣张,为了社会和谐,出台 了一些政策保证:如果有一条航线可以从A_i到B_i,那么保证不可能通过一些道路和航线从B_i回到A_i。由于FJ的奶牛世界公认十分给力,他需要运送奶牛到每一个城镇。他想找到从发送中心城镇S(1 <= S <= T) 把奶牛送到每个城镇的最便宜的方案,或者知道这是不可能的。
输入
第1行:四个空格隔开的整数: T, R, P, and S * 第2到R+1行:三个空格隔开的整数(表示一条道路):A_i, B_i 和 C_i * 第R+2到R+P+1行:三个空格隔开的整数(表示一条航线):A_i, B_i 和 C_i
输出
第1到T行:从S到达城镇i的最小花费,如果不存在输出”NO PATH”。
样例输入
6 3 3 4
1 2 5
3 4 5
5 6 10
3 5 -100
4 6 -100
1 3 -10
样例输出
NO PATH
NO PATH
5
0
-95
-100
分析
求起点到各个点的最短距离,尝试了用SPFA,但是这题数据卡SPFA,不过可以用SLF优化来过,代码贴在后面。
正解:
注意到无向边边权是非负的,这提示我们可以在无向边上跑最短路。并且我们可以知道,如果将无向边连接的点看作一个整体,最后图中只剩下有向边的话,这个图就是一个有向无环图。
所以我们就可以分开考虑,首先求出若干个由无向边组成的连通块,对于块内的点,通过堆优化的dijkstra算法更新最短路;然后在块与块之间,类似于拓扑排序,一层一层地进行更新,最后就可以求出源点s到所有点的最短路了。总的来说就是拓扑排序+dijkstra的方法。
如何划分连通块
可以用在输入了道路之后深度搜索每一个点,因为此时还未读入航线,所以连通块是分开的。
可以将此图划分为三个连通块(block[1] block[2] block [3]),建立一个预处理队列,将s存在的连通块以及入度为0的连通块放入预处理队列中,逐个取出进行处理,先在连通块内部进行dijkstra,当涉及到跨越连通块的边(也就是航线)时,若处理完此边并删去后另一个连通块的入度为0,则将此连通块放入预处理队列(拓扑排序操作)。
拓扑排序+dijkstra代码
#include<bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
typedef long long ll;
const int N=50005,M=100005;
int n,r,p,s;
struct edge{//edge存边
int v,next,w;
}e[M<<1];
struct node{
int cost,u;
bool operator < (const node &A)const{//用于优先队列排序,cost小的优先
return cost>A.cost;
}
};
int head[N],tot;
void adde(int u,int v,int w){//加边
e[tot].v=v,e[tot].w=w,e[tot].next=head[u],head[u]=tot++;
}
bool vis[N];
int cnt;
int bl[N],in[N];//bl存每个点所在的连通块,in存每个连通块的入度
int cost[N];//到每个点的最小花费
vector<int>block[N];//存每个连通块的元素
void dfs(int u)//dfs划分连通块
{
vis[u]=1;
bl[u]=cnt;
block[cnt].push_back(u);
for(int i=head[u];i!=-1;i=e[i].next)
{
int v=e[i].v;
if(vis[v])continue;
dfs(v);
}
}
void solve()//拓扑排序+Dijkstra
{
memset(vis,0,sizeof(vis));
memset(cost,0x7f,sizeof(cost));
cost[s]=0;
queue<int>Q;
Q.push(bl[s]);
for(int i=1;i<=cnt;i++)if(!in[i])Q.push(i);
priority_queue<node>q;
while(!Q.empty())//拓扑排序循环
{
int blo=Q.front();Q.pop();
int blosize=block[blo].size();//将连通块内所有点放入q队列用于Dijkstra
for(int i=0;i<blosize;i++)q.push(node{cost[block[blo][i]],block[blo][i]});
while(!q.empty())//Dijkstra
{
node now=q.top();q.pop();
int u=now.u;
if(vis[u])continue;
vis[u]=1;
for(int i=head[u];i!=-1;i=e[i].next)
{
int v=e[i].v;
if(cost[v]>cost[u]+e[i].w)
{
cost[v]=cost[u]+e[i].w;
if(bl[v]==bl[u])q.push(node{cost[v],v});
}
if(bl[v]!=bl[u]&&(--in[bl[v]])==0)Q.push(bl[v]);//拓扑排序
}
}
}
}
int main()
{
scanf("%d %d %d %d",&n,&r,&p,&s);
memset(head,-1,sizeof(head));
for(int i=1;i<=r;i++)//输入道路
{
int a,b,c;
scanf("%d %d %d",&a,&b,&c);
adde(a,b,c);
adde(b,a,c);
}
for(int i=1;i<=n;i++)//划分连通块
{
if(!vis[i])
{
cnt++;
dfs(i);
}
}
for(int i=1;i<=p;i++)//输入航线
{
int a,b,c;
scanf("%d %d %d",&a,&b,&c);
adde(a,b,c);
in[bl[b]]++;
}
solve();//拓扑排序+Dijkstra
for(int i=1;i<=n;i++)
{
if(cost[i]>INF)printf("NO PATH\n");
else printf("%d\n",cost[i]);
}
return 0;
}
SPFA SLF优化代码
#include<bits/stdc++.h>
using namespace std;
#define N 25010
#define M 150010
#define Inf 0x3f3f3f3f
int e[M],h[N],idx,w[M],ne[M];
int n,r,p,s;
int vis[N];
int cost[N];
deque<int>q;
void addd(int a,int b,int c)
{
e[++idx]=b,ne[idx]=h[a],h[a]=idx,w[idx]=c;
}
void spfa()
{
for(int i=1;i<=n;i++)cost[i]=Inf;
memset(vis,0,sizeof(vis));
vis[s]=1;
q.push_back(s);
cost[s]=0;
while(!q.empty())
{
int now;
now=q.front();
q.pop_front();
vis[now]=0;
for(int i=h[now];i>0;i=ne[i])
{
int u=e[i];
if(cost[u]>cost[now]+w[i])
{
cost[u]=cost[now]+w[i];
if(!vis[u])
{
vis[u]=1;
if(!q.empty()&&cost[q.front()]>cost[u])q.push_front(u);
else q.push_back(u);
}
}
}
}
}
int main()
{
int a,b,c;
scanf("%d %d %d %d",&n,&r,&p,&s);
for(int i=1;i<=r;i++)
{
scanf("%d %d %d",&a,&b,&c);
addd(a,b,c);
addd(b,a,c);
}
for(int i=1;i<=p;i++)
{
scanf("%d %d %d",&a,&b,&c);
addd(a,b,c);
}
spfa();
for(int i=1;i<=n;i++)
{
if(cost[i]==Inf)
{
printf("NO PATH\n");
continue;
}
printf("%d\n",cost[i]);
}
return 0;
}