SPFA求负环初始化可以为任意值
方法一:可以思考一下如果没有负环情况下,最多会松弛多少次,再观察一下松弛操场的特点,发现每次这个操作都是引入一个点来更新长度,显然一个路径不包含重复的点,那么如果松弛了超过n-1次,则必然有一个点被重复引入来松弛,那就不是路径了,那么就有负环,本质原因一条最短路的得出不会松弛(也就是加入点的个数参与更新路径)超过n-1次。这是很显然的,至于n或者n-1,影响不大,因为如果有负环,那么一直经过这个环,结果一直都会小,也就是说会松弛无数次,超过n-1就是无数次了呗。你取一个比n-1多大的值都行,不影响结果准确性,但当然太大就对效率有点影响
1.虫洞 Wormholes
裸求负环
#include <bits/stdc++.h>
using namespace std;
const int N=510,M=5210;
int n,m1,m2;
int h[N],e[M],w[M],ne[M],idx;
int dis[N];
int q[N],cnt[N];
bool st[N];
void add(int a,int b,int c)
{
e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
bool spfa()
{
int hh=0,tt=0;
memset(dis,0x3f,sizeof dis);
memset(st,0,sizeof st);
memset(cnt,0,sizeof cnt);
for(int i=1;i<=n;i++)
{
q[tt++]=i;
st[i]=true;
}
while(hh!=tt)
{
int t=q[hh++];
if(hh==N) hh=0;
st[t]=false;
for(int i=h[t];~i;i=ne[i])
{
int j=e[i];
if(dis[j]>dis[t]+w[i])
{
dis[j]=dis[t]+w[i];
cnt[j]=cnt[t]+1;
if(cnt[j]>=n) return true;
if(!st[j])
{
q[tt++]=j;
if(tt==N) tt=0;
st[j]=true;
}
}
}
}
return false;
}
int main()
{
int t;
cin>>t;
while(t--)
{
cin>>n>>m1>>m2;
memset(h,-1,sizeof h);
idx=0;
while(m1--)
{
int a,b,c;
cin>>a>>b>>c;
add(a,b,c),add(b,a,c);
}
while(m2--)
{
int a,b,c;
cin>>a>>b>>c;
add(a,b,-c);
}
if(spfa()) cout<<"YES"<<endl;
else cout<<"NO"<<endl;
}
return 0;
}
2.观光奶牛(01分数规划+spfa判断负环)
#include <bits/stdc++.h>
using namespace std;
const int N=1010,M=5010;
int n,m;
int wf[N];
int h[N],e[M],wt[M],ne[M],idx;
double dis[N];
int q[N],cnt[N];
bool st[N];
void add(int a,int b,int c)
{
e[idx]=b,wt[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
bool check(double mid)
{
memset(st,0,sizeof st);
memset(cnt,0,sizeof cnt);
int hh=0,tt=0;
for(int i=1;i<=n;i++)
{
q[tt++]=i;
st[i]=true;
}
while(hh!=tt)
{
int t=q[hh++];
if(hh==N) hh=0;
st[t]=false;
for(int i=h[t];~i;i=ne[i])
{
int j=e[i];
if(dis[j]<dis[t]+wf[t]-mid*wt[i])
{
dis[j]=dis[t]+wf[t]-mid*wt[i];
cnt[j]=cnt[t]+1;
if(cnt[j]>=n) return true;
if(!st[j])
{
q[tt++]=j;
if(tt==N) tt=0;
st[j]=true;
}
}
}
}
return false;
}
int main()
{
cin>>n>>m;
memset(h,-1,sizeof h);
for(int i=1;i<=n;i++)
cin>>wf[i];
while(m--)
{
int a,b,c;
cin>>a>>b>>c;
add(a,b,c);
}
double l=0,r=1000;
while(r-l>1e-4)
{
double mid=(l+r)/2;
if(check(mid)) l=mid;
else r=mid;
}
printf("%.2f\n",r);
return 0;
}
3.(单词环)Word Rings
#include<bits/stdc++.h>
using namespace std;
const int N=700,M=1e5+10;
int h[N],e[M],ne[M],w[M],idx;
double dist[N];
int n;
bool st[N];
int cnt[N],q[N];
void add(int a,int b,int c)
{
e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
bool spfa(double mid)
{
memset(st,0,sizeof st);//清空上一层状态
memset(cnt,0,sizeof cnt);//清空上一层状态
int hh=0,tt=0;
int count=0;//用来标记运行的次数
for(int i=0;i<26*26;i++) q[tt++]=i,st[i]=true;//把所有点入队
while(hh!=tt)
{
int t=q[hh++];
if(hh==N) hh=0;//循环队列
st[t]=false;//标记这个点出队了
for(int i=h[t];~i;i=ne[i])
{
int j=e[i];
if(dist[j]<dist[t]+w[i]-mid)//假如满足01分数规划分析的
{
dist[j]=dist[t]+w[i]-mid;
cnt[j]=cnt[t]+1;//更新走的边数
if(cnt[j]>=N) return true;//假如大于全部的点了,说明有的点已经重复走过了,有环
if(++count>2*n) return true;//假如运行超过最大边数的两倍也说明有环
if(!st[j])//假如不在队列中
{
q[tt++]=j;
if(tt==N) tt=0;
st[j]=true;
}
}
}
}
return false;//则没有环
}
int main()
{
char str[1010];
while(scanf("%d",&n),n)
{
memset(h,-1,sizeof h);
idx=0;
for(int i=0;i<n;i++)
{
scanf("%s",str);
int len=strlen(str);
if(len<2) continue;
int a=(str[0]-'a')*26+(str[1]-'a'),b=(str[len-2]-'a')*26+(str[len-1]-'a');//把两个字母对应到26进制中
add(a,b,len);//把a->b连接起来边权是串的长度
}
if(!spfa(0))//假如0都不通过则就是没环了
{
puts("No solution");
continue;
}
double l=0,r=1000;
while(r-l>1e-4)//二分
{
double mid=(l+r)/2;
if(spfa(mid)) l=mid;//假如满足
else r=mid;
}
printf("%lf\n",l);
}
return 0;
}
栈优化就是把队列变成栈就行了
#include<bits/stdc++.h>
using namespace std;
const int N=700,M=1e5+10;
int h[N],e[M],ne[M],w[M],idx;
double dist[N];
int n;
bool st[N];
int cnt[N],q[N];
void add(int a,int b,int c)
{
e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
bool spfa(double mid)
{
memset(st,0,sizeof st);//清空上一层状态
memset(cnt,0,sizeof cnt);//清空上一层状态
int tt=0;
for(int i=0;i<26*26;i++) q[tt++]=i,st[i]=true;//把所有点入队
while(tt!=0)
{
int t=q[--tt];
st[t]=false;//标记这个点出队了
for(int i=h[t];~i;i=ne[i])
{
int j=e[i];
if(dist[j]<dist[t]+w[i]-mid)//假如满足01分数规划分析的
{
dist[j]=dist[t]+w[i]-mid;
cnt[j]=cnt[t]+1;//更新走的边数
if(cnt[j]>=N) return true;//假如大于全部的点了,说明有的点已经重复走过了,有环
if(!st[j])//假如不在队列中
{
q[tt++]=j;
st[j]=true;
}
}
}
}
return false;//则没有环
}
int main()
{
char str[1010];
while(scanf("%d",&n),n)
{
memset(h,-1,sizeof h);
idx=0;
for(int i=0;i<n;i++)
{
scanf("%s",str);
int len=strlen(str);
if(len<2) continue;
int a=(str[0]-'a')*26+(str[1]-'a'),b=(str[len-2]-'a')*26+(str[len-1]-'a');//把两个字母对应到26进制中
add(a,b,len);//把a->b连接起来边权是串的长度
}
if(!spfa(0))//假如0都不通过则就是没环了
{
puts("No solution");
continue;
}
double l=0,r=1000;
while(r-l>1e-4)//二分
{
double mid=(l+r)/2;
if(spfa(mid)) l=mid;//假如满足
else r=mid;
}
printf("%lf\n",l);
}
return 0;
}