SPFA优化,判负环(刷题)
知识点:
1.SLF 优化
双端队列,每次入队的时候比较入队的元素和队首哪个优,这个元素优就从队首入队,否则插入队尾
2.有向图中统计最长路
从起点开始拓扑排序,跑到终点,求最长路
3.双调路径
路径有两个限制条件,例如 双调路径,每条路径有时间和金钱,求哪条路径更优
\(dis\) 开二维,背包 \(dp\) 思想,花费少的肯定在优先考虑,后面的如果想更优那么只有时间更短,枚举金钱即可
4.\(SPFA\) 判负环
若果没有负环,判断正环要用 \(dijkstra\)
根据 \(SPFA\) 原理的松弛操作,每个点最多被更新 \(n\) 次,用 $cnt[ i ] $ 表示从起点(假设就是 1)到 \(i\) 的最短距离包含点的个数,初始化 \(cnt[ 1 ] = 1\),那么当我们能够用点 \(u\) 松弛点 \(v\) 时,松弛时同时更新 \(cnt[ v ] = cnt[ u ] + 1\),若发现此时 \(cnt[ v ] > n\),那么就存在负环
比判断入队次数比较快很多
Roads and Planes G
单元最短路,有负边,无负环,部分双向边,部分无向边(spfa会卡)
solution
/*
work by:Ariel_
*/
#include <bits/stdc++.h>
#include <iostream>
#include <cstdio>
#include <queue>
#include <stack>
#include<climits>
const int N = 1e5 + 5;
using std::deque;
deque <int> q;
int read(){
int x = 0,f = 1;char c = getchar();
while(c < '0'||c > '9'){if(c == '-')f = -1;c = getchar();}
while(c >= '0'&&c <= '9'){x = x*10 + c - '0';c = getchar();}
return f*x;
}
struct edge{
int v,nxt,w;
}e[N << 1];
int cnt,head[N];
void add_edge(int u,int v,int w){
e[++cnt] = (edge){v, head[u], w};
head[u] = cnt;
}
int n, r, p, s, dis[N];
bool vis[N];
void spfa(int s){
memset(dis,0x3f3f3f3f, sizeof(dis));
dis[s] = 0;vis[s] = 1;
q.push_back(s);
while(!q.empty()){
int u = q.front();q.pop_front();
for(int i = head[u]; i; i = e[i].nxt){
int v = e[i].v;
if(dis[v] > dis[u] + e[i].w){
dis[v] = dis[u] + e[i].w;
if(!vis[v]){
if(!q.empty() && dis[v] >= dis[q.front()])q.push_back(v);
else q.push_front(v);
vis[v] = 1;
}
}
}
vis[u] = 0;
}
}
int main(){
n = read(),r = read(),p = read(),s = read();
for(int i = 1,u, v, w;i <= r; i++){
u = read(),v = read(),w = read();
add_edge(u, v, w),add_edge(v, u, w);
}
for(int i = 1,u, v, w;i <= p; i++){
u = read(),v = read(),w = read(),add_edge(u, v, w);
}
spfa(s);
for(int i = 1;i <= n; i++){
if(dis[i] != 0x3f3f3f3f)printf("%d\n",dis[i]);
else printf("NO PATH\n");
}
}
最长路
设 \(G\) 为有 \(n\) 个顶点的带权有向无环图,\(G\) 中各顶点的编号为 \(1\) 到 \(n\),请设计算法,计算图 \(G\) 中 \(<1,n>\) 间的最长路径。
solution
因为到达n点的值只能用从 \(1\) 出发的路径更新,所以直接拓扑排序,求最长路即可
/*
work by:Ariel_
*/
#include <iostream>
#include <cstdio>
#include <queue>
#include <cstring>
using namespace std;
const int M = 500500;
int read(){
int x = 0,f = 1;char c = getchar();
while(c < '0'||c > '9'){if(c == '-')f = -1;c = getchar();}
while(c >= '0'&&c <= '9'){x = x*10 + c - '0';c = getchar();}
return f*x;
}
int ru[M];
struct edge{
int v, nxt, w;
}e[M << 1];
int cnt,head[M];
void add_edge(int u,int v,int w){
e[++cnt] = (edge){v, head[u], w};
head[u] = cnt;
}
int n, m,vis[M],dis[M];
void topu(){
queue<int> q;
for(int i = 1;i <= n; i++)
if(!ru[i])q.push(i);
dis[n] = -1;vis[1] = 1;
while(!q.empty()){
int u = q.front();
q.pop();
for(int i = head[u]; i;i = e[i].nxt){
int v = e[i].v;
ru[v]--;
if(vis[u] == 1){
if(dis[v] < dis[u] + e[i].w)
dis[v] = dis[u] + e[i].w;
vis[v] = 1;
}
if(!ru[v]) q.push(v);
}
}
}
int main(){
n = read(),m = read();
for(int i = 1,u, v, w;i <= m; i++){
u = read(),v = read(),w = read();
ru[v]++;
add_edge(u, v, w);
}
topu();
printf("%d",dis[n]);
}
双调路径
\(n\) 个点 \(m\) 条边的无向图,每条边有两个限制条件(时间(距离)和金钱),求最短路条数
solution
很显然,时间越短且花费越少的路径更优,和一般的最短路区别是,一般的最短路只求距离,而这个有两个限制,所以 \(dis\) 数组应该开两维,第一维是目标点,第二维是到这个点所花费的钱,存的是到这个点的所需要的总共的时间,跑最短路的时候枚举所花费的钱即可;
/*
work by:Ariel_
*/
#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#include <bits/stdc++.h>
const int M = 310;
int read(){
int x = 0,f = 1;char c = getchar();
while(c < '0'||c > '9'){if(c == '-')f = -1;c = getchar();}
while(c >= '0'&&c <= '9'){x = x*10 + c - '0';c = getchar();}
return f*x;
}
using std::queue;
struct edge{
int v, nxt, w, t;
}e[100010];
int cnt,head[110];
void add_edge(int u,int v,int w,int t){
e[++cnt] = (edge){v, head[u], w, t};
head[u] = cnt;
}
int n, m, s, z,dis[110][100010];
bool vis[110];
void spfa(int s){
queue <int> q;
memset(dis, 0x3f, sizeof(dis));
for(int i = 1;i <= 10000; i++)dis[s][i] = 0;
q.push(s);vis[s] = 1;
while(!q.empty()){
int u = q.front();
q.pop();vis[u] = 0;
for(int i = head[u]; i;i = e[i].nxt){
int v = e[i].v,w = e[i].w,t = e[i].t;
for(int j = 0;j <= 10000 - w; j++){
if(dis[v][j + w] > dis[u][j] + t){
dis[v][j + w] = dis[u][j] + t;
if(!vis[v]){
vis[v] = 1;q.push(v);
}
}
}
}
}
}
int main(){
n = read(),m = read(),s = read(),z = read();
for(int i = 1;i <= m; i++){
int u = read(),v = read(),w = read(),t = read();
add_edge(u, v, w, t);
add_edge(v, u, w, t);
}
spfa(s);
int qc = 99999999,ans = 0;
for(int i = 0;i <= 100000; i++){
if(dis[z][i] < qc)
qc = dis[z][i],ans++;
}
printf("%d", ans);
}
最小圈
求一个有向图中,环的最小平均值,注意有负环
solution
二分平均值
若我们此时枚举的平均值为 \(ans\),有 \(k\) 个字符串,那么就有
\(ans * k = len1 + len2 + len3 + ... + lenk\)
那道这个式子之后,我们对它进行移项
\(0=len1-ans+len2-ans+len3-ans+...+lenk-ans\)
那么对于满足以下式子,就可以判断是环了,所以在跑 \(SPFA\) 新距离的时候,就应该像下面这样
\(0 \leq len1-ans+len2-ans+len3-ans+...+lenk-ans\)
/*
work by:Ariel_
*/
#include <iostream>
#include <cstdio>
#include <queue>
#include <cstring>
const int N = 1e4 + 5;
const int M = 3e7 + 5;
const double eps = 1e-9;
const double inf = (1e5)*1.0;
int read(){
int x = 0,f = 1;char c = getchar();
while(c < '0'||c > '9'){if(c == '-')f = -1;c = getchar();}
while(c >= '0'&&c <= '9'){x = x*10 + c - '0';c = getchar();}
return f*x;
}
int n, m;
struct edge{
int v, nxt;
double w;
}e[M];
int cnt,head[M];
void add_edge(int u, int v, double w){
e[++cnt] = (edge){v, head[u], w};
head[u] = cnt;
}
double dis[M];
bool vis[M];
bool dfs(int s,double x){
vis[s] = 1;
for(int i = head[s]; i;i = e[i].nxt){
int v = e[i].v;
if(dis[v] > dis[s] + e[i].w - x){
dis[v] = dis[s] + e[i].w - x;
if(vis[v]||dfs(v, x))return true;
}
}
vis[s] = 0;
return false;
}
bool check(double mid){
for(register int i = 1; i <= n; i++)
dis[i] = 0x3f3f,vis[i] = 0;
for(int i = 1;i <= n; i++)
if(dfs(i, mid)) return true;
return false;
}
int main(){
n = read(),m = read();
for(int i = 1,u, v;i <= m; i++){
double w;
u = read(),v = read();
std::cin >> w;
add_edge(u, v, w);
}
double l = -inf,r = inf;
while(l + eps < r){
double mid = (l + r) / 2;
if(check(mid)) r = mid;
else l = mid;
}
printf("%.8lf", l);
}
Wormholes G
判断图中是否存在负环
solution
\(SPFA\) 判负环,刚开始写的 \(bfs\) 如果被入队次数为\(>=n\) 那么就存在负环,后来写了遍 \(dfs\) ,快了三倍
bfs
/*
work by:Ariel_
*/
#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
int read(){
int x = 0,f = 1;char c = getchar();
while(c < '0'||c > '9'){if(c == '-')f = -1;c = getchar();}
while(c >= '0'&&c <= '9'){x = x*10 + c - '0';c = getchar();}
return f*x;
}
int n, m, T, z;
struct edge{
int v, nxt, w;
}e[100000];
int head[10000],js;
void add_edge(int u,int v,int w){
e[++js] = (edge){v, head[u], w};
head[u] = js;
}
using std::queue;
int vis[1000],dis[1000],cnt[1000];
bool spfa(int x){
queue<int> q;
vis[x] = 1;dis[x] = 0;
q.push(x);
while(!q.empty()){
int u = q.front();
q.pop();
vis[u] = 0;
for(int i = head[u]; i;i = e[i].nxt){
int v = e[i].v;
if(dis[v] > dis[u] + e[i].w){
dis[v] = dis[u] + e[i].w;
if(!vis[v]){
q.push(v);
vis[v] = 1;
cnt[v] = cnt[u] + 1;
}
if(cnt[v] > n)return true;
}
}
}
return false;
}
void clear()
{
memset(head,-1,sizeof(head));
memset(e,0,sizeof(e));
memset(cnt,0,sizeof(cnt));
memset(vis,0,sizeof(vis));
memset(dis,0x3f,sizeof(dis));
}
int main(){
T = read();
while(T--)
{
int c;
clear();
n = read(),m = read(),c = read();
for(int i = 1;i <= m; i++){
int x,y,z;
x = read(),y = read(),z = read();
add_edge(x,y,z),add_edge(y,x,z);
}
for(int i = 1;i <= c;i ++){
int x,y,z;
x = read(),y = read(),z = read();
add_edge(x, y, -z);
}
if(spfa(1))printf("YES\n");
else printf("NO\n");
}
}
dfs
/*
work by:Ariel_
*/
#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
int read(){
int x = 0,f = 1;char c = getchar();
while(c < '0'||c > '9'){if(c == '-')f = -1;c = getchar();}
while(c >= '0'&&c <= '9'){x = x*10 + c - '0';c = getchar();}
return f*x;
}
int n, m, T, z;
struct edge{
int v, nxt, w;
}e[100000];
int head[10000],js;
void add_edge(int u,int v,int w){
e[++js] = (edge){v, head[u], w};
head[u] = js;
}
int vis[1000],dis[1000],cnt[1000];
int flag;
bool spfa(int x,int h){
if(flag)return true;
vis[x] = h;
for(int i = head[x]; i;i = e[i].nxt){
if(flag)return true;
int v = e[i].v;
if(dis[v] > dis[x] + e[i].w){
dis[v] = dis[x] + e[i].w;
if(!vis[v])spfa(v, h);
if(vis[v] == h)return true;
}
}
vis[x] = 0;
return false;
}
void clear()
{
js = 0;
flag = 0;
memset(head,-1,sizeof(head));
memset(e,0,sizeof(e));
memset(vis,0,sizeof(vis));
memset(dis,0,sizeof(dis));
}
int main(){
T = read();
while(T--)
{
int c;
clear();
n = read(),m = read(),c = read();
for(int i = 1;i <= m; i++){
int x,y,z;
x = read(),y = read(),z = read();
add_edge(x,y,z),add_edge(y,x,z);
}
for(int i = 1;i <= c;i ++){
int x,y,z;
x = read(),y = read(),z = read();
add_edge(x, y, -z);
}
int z = 0;
for(int i = 1;i <= n; i++){
if(spfa(i,i)){
printf("YES\n");z = 1;break;
}
}
if(!z)printf("NO\n");
}
}
Easy SSSP
判负环同时求最短路
solution
SPFA 判环模板?,不对劲,有的环不在起点,求构成了环,坑点1
于是成了每个点,发现T了,只好写 \(dfs\)
/*
work by:Ariel_
*/
#include <iostream>
#include <cstdio>
#include <queue>
#include <cstring>
using namespace std;
const int M = 1e5 + 10;
const int N = 1010;
int read(){
int x = 0,f = 1;char c = getchar();
while(c < '0'||c > '9'){if(c == '-')f = -1;c = getchar();}
while(c >= '0'&&c <= '9'){x = x*10 + c - '0';c = getchar();}
return f*x;
}
struct edge{
int v,nxt,w;
}e[M << 1];
int js,head[M];
void add_edge(int u,int v,int w){
e[++js] = (edge){v,head[u],w};
head[u] = js;
}
int vis[N],dis[N],tot[N];
int n, m, s;
void clear(){
for(int i = 1;i <= n;i++)dis[i] = 999999999;
memset(vis, 0,sizeof(vis));
memset(tot, 0,sizeof(tot));
}
queue<int>q;
bool flag;
bool SPFA(int x,int h){
vis[x] = h;
for(int i = head[x]; i;i = e[i].nxt){
int v = e[i].v;
if(dis[v] > dis[x] + e[i].w){
dis[v] = dis[x] + e[i].w;
if(!vis[v])SPFA(v, h);
if(flag)return true;
else if(vis[v] == h){
return true;
}
}
}
vis[x] = 0;
return false;
}
bool spfa(int x){
clear();
dis[x] = 0,vis[x] = 1;q.push(x);
while(!q.empty()){
int u = q.front();q.pop();
vis[u] = 0;
for(int i = head[u]; i;i = e[i].nxt){
int v = e[i].v;
if(dis[v] > dis[u] + e[i].w){
dis[v] = dis[u] + e[i].w;
if(!vis[v]){
vis[v] = 1;
q.push(v);
tot[v] = tot[u] + 1;
}
if(tot[v] >= n) return true;
}
}
}
return false;
}
int main(){
n = read(),m = read(),s = read();
for(int i = 1,u, v, w;i <= m; i++){
u = read(),v = read(),w = read();
add_edge(u, v, w);
}
clear();
for(int i = 1;i <= n; i++){
if(SPFA(i, i)){
printf("-1");return 0;
}
}
if(!spfa(s)){
for(int i = 1;i <= n; i++){
if(dis[i] == 999999999){
printf("NoPath\n");continue;
}
else printf("%d\n",dis[i]);
}
}
}