负环
负环的判定比较简单,直接用spfa即可。在求负环或是正环时要注意一个点:
要提前将所有点入队并更新其dist值为0。为什么要这么做呢?
1.任意挑选的起点有可能与环不连通,所以要加入虚拟原点s,并将其与所有点相连
2.初值置为0不会影响最短路的长度
例题1:AcWing361.观光奶牛
这题比较简单,考虑用0/1分数规划求解,代码如下:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
const int N=1005,M=5005;
const double eps=1e-4;
int n,m;
int head[N],to[M],ne[M],idx;
double w[M],f[N];
struct Edge{
int x,y;
double z;
}edge[M];
void add(int x,int y,double z){
ne[++idx]=head[x];
to[idx]=y;
w[idx]=z;
head[x]=idx;
}
queue<int> q;
double dist[N];
bool st[N];
int cnt[N];
bool spfa(){
memset(cnt,0,sizeof cnt);
memset(st,false,sizeof st);
for(int i=1;i<=n;i++){
q.push(i);
dist[i]=0.0;
}
while(q.size()){
int t=q.front();
q.pop();
st[t]=false;
for(int i=head[t];i;i=ne[i]){
int j=to[i];
if(dist[j]>dist[t]+w[i]){
dist[j]=dist[t]+w[i];
cnt[j]=cnt[t]+1;
if(cnt[j]>=n) return true;
if(!st[j]){
q.push(j);
st[j]=true;
}
}
}
}
return false;
}
bool check(double mid){
memset(head,0,sizeof head);
memset(to,0,sizeof to);
memset(ne,0,sizeof ne);
idx=0;
for(int i=1;i<=m;i++){
int x=edge[i].x,y=edge[i].y;
double z=mid*edge[i].z-f[x];
add(x,y,z);
}
return spfa();
}
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>f[i];
for(int i=1;i<=m;i++){
int x,y;
double z;
cin>>x>>y>>z;
edge[i]={x,y,z};
}
double l=0,r=1e6;
while(r-l>eps){
double mid=(l+r)/2;
if(check(mid)) l=mid;
else r=mid;
}
printf("%.2lf",l);
return 0;
}
扩展设问:求该比值的最小值?
差分约束
这个算法常用于解决多元一次不等式组(但前提是每个不等式都要可以整理成xi-xj
≤
\leq
≤c的形式)。其难点在于不等关系的寻找以及一些对于未知数隐含的限制条件。
例题1:AcWing 362.区间
这个题目还是很有思维量。首先想到将区间与个数的不等关系转化成两个未知数的差与另一个常数之间的不等关系,这便联想到前缀和。即设xi为值为i的数有多少个,sumi为x数组的前缀和。那么不等关系就巧妙地转化为了sumb-suma-1
≥
\geq
≥c。然而到这里还没完,对于每个sumi还有限制,首先它自身大小至多为1(集合内元素不可重复),且sumi
≥
\geq
≥sumi-1。代码如下:
#include<iostream>
#include<cstring>
#include<queue>
using namespace std;
const int N=50005;
int head[N],to[3*N],ne[3*N],w[3*N],idx;
void add(int x,int y,int z){
ne[++idx]=head[x];
to[idx]=y;
w[idx]=z;
head[x]=idx;
}
int dist[N];
bool st[N];
void spfa(){
queue<int> q;
q.push(0);
memset(dist,-0x3f,sizeof dist);
dist[0]=0;
while(q.size()){
int t=q.front();
q.pop();
st[t]=false;
for(int i=head[t];i;i=ne[i]){
int j=to[i];
if(dist[j]<dist[t]+w[i]){
dist[j]=dist[t]+w[i];
if(!st[j]){
q.push(j);
st[j]=true;
}
}
}
}
}
int main(){
int n;
cin>>n;
for(int i=1;i<=50001;i++){
add(i-1,i,0);
add(i,i-1,-1);
}
for(int i=1;i<=n;i++){
int a,b,c;
cin>>a>>b>>c;
a++,b++;
add(a-1,b,c);
}
spfa();
cout<<dist[50001]<<endl;
return 0;
}
一个比较重要的补充点:
差分约束不是可以随便转化求最长路或者最短路的,要根据题意。本题要求最小值。那么我们直到ans
≥
\geq
≥m。对于一堆这样形式的不等式,我们必然会取m的最大值作为ans的最小值,也就是求最长路。