题目描述

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

样例输入解释:一共六个城镇。在1-2,3-4,5-6之间有道路,花费分别是5,5,10。同时有三条航线:3->5, 4->6和1->3,花费分别是-100,-100,-10。FJ的中心城镇在城镇4。

样例输出

NO PATH

NO PATH

5

0

-95

-100

样例输出解释:FJ的奶牛从4号城镇开始,可以通过道路到达3号城镇。然后他们会通过航线达到5和6号城镇。 但是不可能到达1和2号城镇。

分析

正解

我们可以发现题目中有两种边,一种是双向边,边权非负,另一种是单向边,边权可能为正

如果我们用Dij直接去跑最短路,显然是不可以的,因为题目中有负权

如果我们用SPFA 呢,显然会被卡掉

所以我们考虑一下它所具有的的某种性质

双向建的边是非负的,跑Dij是没有问题的,但是问题就是题目中还有单项负权边

我们仔细读一下题就可以发现一个重要的性质:负权的边不会出现环

那么我们就可以把强连通分量缩点,这样缩点之后的图就变成了一个有向无环图

这样就可以在强连通分量内使用Dij,分量外使用拓扑排序更新答案

代码

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<queue>
#include<vector>
#define INF 0x3f3f3f3f
using namespace std;
const int maxn=150005;
int t,r,p,s,head[maxn],tot=1,cnt;
struct asd{
int to,next,val;
}b[maxn];
void ad(int aa,int bb,int cc){
b[tot].to=bb;
b[tot].val=cc;
b[tot].next=head[aa];
head[aa]=tot++;
}
bool vis[maxn];
int shuyu[maxn],dis[maxn];
vector<int> jl[maxn];
void dfs(int now){
shuyu[now]=cnt,vis[now]=1,jl[cnt].push_back(now);
for(int i=head[now];i!=-1;i=b[i].next){
int u=b[i].to;
if(vis[u])continue;
dfs(u);
}
}
struct jie{
int num,jz;
jie(int aa=0,int bb=0){
num=aa,jz=bb;
}
bool operator < (const jie& A) const {
return jz>A.jz;
}
};
int ru[maxn];
queue<int> q;
priority_queue<jie> Q;
void dij(){
dis[s]=0;
while(!q.empty()) {
int xx=q.front();
q.pop();
for(int i=0;i<jl[xx].size();i++){
Q.push(jie(jl[xx][i],dis[jl[xx][i]]));
}
while(!Q.empty()){
int now = Q.top().num;
Q.pop();
if(vis[now]) continue;
vis[now]=1;
for(int i=head[now];i!=-1;i=b[i].next){
int u=b[i].to;
if(dis[u]>dis[now]+b[i].val){
dis[u]=dis[now]+b[i].val;
if(shuyu[now]==shuyu[u]) Q.push(jie(u,dis[u]));
}
if(shuyu[u]!=shuyu[now] && (--ru[shuyu[u]]==0)) q.push(shuyu[u]);
}
}
}
}
int main(){
memset(head,-1,sizeof(head));
scanf("%d%d%d%d",&t,&r,&p,&s);
for(int i=1;i<=r;i++){
int aa,bb,cc;
scanf("%d%d%d",&aa,&bb,&cc);
ad(aa,bb,cc);
ad(bb,aa,cc);
}
for(int i=1;i<=t;i++){
if(!vis[i]) cnt++,dfs(i);
}
for(int i=1;i<=p;i++){
int aa,bb,cc;
scanf("%d%d%d",&aa,&bb,&cc);
ad(aa,bb,cc);
ru[shuyu[bb]]++;
}
memset(vis,0,sizeof(vis));
memset(dis,0x7f,sizeof(dis));
for(int i=1;i<=cnt;i++) if(ru[i]==0) q.push(i);
dij();
for(int i=1;i<=t;i++){
if(dis[i]>INF) printf("NO PATH\n");
else printf("%d\n",dis[i]);
}
return 0;
}

水过

当然,如果是在考试的时候,正解肯定是很难想到

即使是想到了,又是缩点又是拓扑排序,又是Dij,分起来写还好点,如果合在一起,难免有点代码量

而且,如果最后你调试了半天还没有暴力分高,岂不是很尴尬

所以我们就尝试这用SPFA水一下

其实SPFA本质上还是Bellman Ford的优化版本

那么Bellman Ford是怎么运作的呢

实际上它是使用全部的边对于起点到其他n-1个点的路径进行松弛,重复n-1次

算法复杂度为O(VE)

这样的复杂度几千条边还勉强可以接受,但是十万以上的边是肯定不可以的

于是就有了优化版本SPFA,它的优化之处在哪里呢

实际上我们来想一下,对于普通的Bellman Ford,其实有些边是根本松弛不动的

所以我们优化的方向就是把肯定不能松弛其它节点的节点排除在外

我们不去考虑哪些节点不能松弛其它节点,而是考虑哪些节点可以松弛其它节点

很显然,只有当前已经松弛成功过的节点才有可能松弛其它的节点

因此这时,我们就用一个栈来记录那些已经松弛成功的节点

每次我们只要从栈中取出节点松弛就可以了

那么时间复杂度呢?

SPFA算法的时间复杂度是不可靠的,一般情况下为O(E),而在极限情况下也有可能达到Bellman-ford算法的复杂度,即O(V*E)

其实,想要把SPFA卡掉还是很容易的,我们可以随便建一个网格图

因为网格图中的边比较稠密,所以SPFA稍有不慎变会误入歧途,然后多走很多条边

但是,网上也有很多关于SPFA的优化,其实就是想让普通的栈更接近优先队列

因为如果你的栈里有很多个已经松弛过的节点,你肯定希望拿出一个值比较小的节点去松弛其他的节点

因为这样一次松弛成功的几率比较大

所以,我们尽量使维护的栈更接近一个优先队列,也就是权值小的先出栈

目前比较常见的有两种方法,一种是把要插入的元素的值和栈顶元素比较,如果比栈顶元素小,那么就把这个元素放在占栈顶,否则放在栈底

另一种方法就是把与队首元素比较改成了与队中元素的平均值比较,思路差不多

对于这道题,我们可以用第一种方法水过

#include<cstdio>
#include<iostream>
#include<queue>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxn=150005;
struct asd{
ll from,to,next,val;
}b[maxn];
ll head[maxn],tot=1;
void ad(ll aa,ll bb,ll cc){
b[tot].from=aa;
b[tot].to=bb;
b[tot].next=head[aa];
b[tot].val=cc;
head[aa]=tot++;
}
deque<ll> q;
bool vis[maxn];
ll dis[maxn];
void SPFA(ll xx){
memset(dis,0x3f,sizeof(dis));
dis[xx]=0,vis[xx]=1;
q.push_back(xx);
while(!q.empty()){
ll now=q.front();
q.pop_front();
vis[now]=0;
for(ll i=head[now];i!=-1;i=b[i].next){
ll u=b[i].to;
if(dis[u]>dis[now]+b[i].val){
dis[u]=dis[now]+b[i].val;
if(!vis[u]){
if(!q.empty()&&dis[u]>=dis[q.front()]) q.push_back(u);
else q.push_front(u);
vis[u]=1;
}
}
}
}
}
int main(){
memset(head,-1,sizeof(head));
ll t,r,p,s;
scanf("%lld%lld%lld%lld",&t,&r,&p,&s);
for(ll i=1;i<=r;i++){
ll aa,bb,cc;
scanf("%lld%lld%lld",&aa,&bb,&cc);
ad(aa,bb,cc),ad(bb,aa,cc);
}
for(ll i=1;i<=p;i++){
ll aa,bb,cc;
scanf("%lld%lld%lld",&aa,&bb,&cc);
ad(aa,bb,cc);
}
SPFA(s);
for(ll i=1;i<=t;i++){
if(dis[i]==0x3f3f3f3f3f3f3f3f) printf("NO PATH\n");
else printf("%lld\n",dis[i]);
}
return 0;
}

其实大家还可以想一下,如果我们把用优先队列,改成用小根堆去维护,会变成什么样子

或者是裸的Bellman Ford加点特判,又会是什么样子

最后提醒大家一下,正权最短路尽量用DIJ

负权最短路和最长路千万不要用DIJ,卡成0分都有可能

P3008 [USACO11JAN]Roads and Planes G 拓扑排序+Dij的更多相关文章

  1. P3008 [USACO11JAN]Roads and Planes G (最短路+拓扑排序)

    该最短路可不同于平时简单的最短路模板. 这道题一看就知道用SPFA,但是众所周知,USACO要卡spfa,所以要用更快的算法. 单向边不构成环,双向边都是非负的,所以可以将图分成若干个连通块(内部只有 ...

  2. [USACO11JAN]Roads and Planes G【缩点+Dij+拓补排序】

    题目 Farmer John正在一个新的销售区域对他的牛奶销售方案进行调查.他想把牛奶送到T个城镇 (1 <= T <= 25,000),编号为1T.这些城镇之间通过R条道路 (1 < ...

  3. 【图论】USACO11JAN Roads and Planes G

    题目内容 洛谷链接 Farmer John正在一个新的销售区域对他的牛奶销售方案进行调查.他想把牛奶送到\(T\)个城镇 (\(1 <= T <= 25,000\)),编号为\(1\)到\ ...

  4. [USACO11JAN]Roads and Planes

    嘟嘟嘟 这道题他会卡spfa,不过据说加SLF优化后能过,但还是讲讲正解吧. 题中有很关键的一句,就是无向边都是正的,只有单向边可能会有负的.当把整个图缩点后,有向边只会连接在每一个联通块之间(因为图 ...

  5. Wannafly挑战赛2 D.Delete(拓扑排序 + dij预处理 + 线段树维护最小值)

    题目链接  D.Delete 考虑到原图是个DAG,于是我们可以求出每个点的拓扑序. 然后预处理出起点到每个点的最短路$ds[u]$, 和所有边反向之后从终点出发到每个点的最短路$dt[u]$. 令点 ...

  6. 拓扑排序(三)之 Java详解

    前面分别介绍了拓扑排序的C和C++实现,本文通过Java实现拓扑排序. 目录 1. 拓扑排序介绍 2. 拓扑排序的算法图解 3. 拓扑排序的代码说明 4. 拓扑排序的完整源码和测试程序 转载请注明出处 ...

  7. 拓扑排序(二)之 C++详解

    本章是通过C++实现拓扑排序. 目录 1. 拓扑排序介绍 2. 拓扑排序的算法图解 3. 拓扑排序的代码说明 4. 拓扑排序的完整源码和测试程序 转载请注明出处:http://www.cnblogs. ...

  8. 拓扑排序(一)之 C语言详解

    本章介绍图的拓扑排序.和以往一样,本文会先对拓扑排序的理论知识进行介绍,然后给出C语言的实现.后续再分别给出C++和Java版本的实现. 目录 1. 拓扑排序介绍 2. 拓扑排序的算法图解 3. 拓扑 ...

  9. P3008 [USACO11JAN]道路和飞机Roads and Planes

    P3008 [USACO11JAN]道路和飞机Roads and Planes Dijkstra+Tarjan 因为题目有特殊限制所以不用担心负权的问题 但是朴素的Dijkstra就算用堆优化,也显然 ...

随机推荐

  1. centos 7 源码安装openssh

    环境:centos 7.1.1503 最小化安装 依赖包下载:  yum -y install lrzsz zlib-devel perl gcc pam-devel 1.安装openssl ,选用最 ...

  2. Centos6.5--svn搭建

    0x01 配置好镜像源安装svn yum install subversion -y 0x02 安装完成之后在/opt下面新建一个svn的目录,当然也可以在其他的地方建立svn目录,这个看个人爱好. ...

  3. Nlog打印日志到Influxdb数据库

    1.安装和使用Influxdb 安装部分网上资料比较多,也讲的比较详细,请自行百度. 下面大概讲下InfluxDB的写入和读取数据的方法. 我使用了InfluxData.Net包. 工具->Nu ...

  4. 658.找到K个最接近的元素

    2020-03-10 找到 K 个最接近的元素 给定一个排序好的数组,两个整数 k 和 x,从数组中找到最靠近 x(两数之 差最小)的 k 个数.返回的结果必须要是按升序排好的.如果有两个数与 x 的 ...

  5. (一)HttpClient Get请求

    原文链接:https://blog.csdn.net/justry_deng/article/details/81042379 HttpClient的主要功能: 实现了所有 HTTP 的方法(GET. ...

  6. 使用 LIKE 的模糊查询

    字符串匹配的语法格式如下: <表达式1> [NOT] LIKE <表达式2> 字符串匹配是一种模式匹配,使用运算符 LIKE 设置过滤条件,过滤条件使用通配符进行匹配运算,而不 ...

  7. CKAD个人考试心得

    先晒一波本人的CKA和CKAD证书! 如下正式分享CKAD心得: 考试相关准备: l 练习:https://github.com/dgkanatsios/CKAD-exercises: l 网络:必须 ...

  8. 宝塔面板搭载yii2.0项目关于open_basedir报错解决办法

    昨天配置完宝塔的lamp后,然后把原本的yii项目放上去,发现出现三个报错,就是大概  require openssl之类的三个错误 然后去宝塔的界面里去配置了一个端口,然后再去阿里云上开放这个端口 ...

  9. vulstack红队评估(一)

    一.环境搭建: 1.根据作者公开的靶机信息整理: 虚拟机初始所有统一密码:hongrisec@2019   因为登陆前要修改密码,改为了panda666...   2.虚拟网卡网络配置: ①Win7双 ...

  10. 他被称为"中国第一程序员",微软得不到他曾想毁了他,如今拜入武当修道

    GitHub 15.4k Star 的Java工程师成神之路,不来了解一下吗! GitHub 15.4k Star 的Java工程师成神之路,真的不来了解一下吗! GitHub 15.4k Star ...