Loading

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]);
	   }
	}

}

posted @ 2021-02-01 21:27  Dita  阅读(120)  评论(0编辑  收藏  举报