单源最短路径
TZOJ:8537
求一个点到其他所有点的最短距离,无负权边
Dijkstra算法(优化)
用一个小顶堆去优化,每次弹出一个未标记过的最近的点,不能处理有负权边的情况
O(mlongm)
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e4+10;
vector<pair<int,int> >a[N];
priority_queue<pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > >p;
int n,m,t;
int dis[N],bj[N];
signed main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
cin>>n>>m>>t;
for(int i=1;i<=m;i++)
{
int x,y,z;
cin>>x>>y>>z;
a[x].push_back({y,z});
}
for(int i=1;i<=n;i++)dis[i]=(1LL<<31)-1;
dis[t]=0;
for(int i=1;i<=n;i++)p.push({dis[i],i});
int num=0;
while(!p.empty())
{
num++;
if(num==n+1)break;
int x,Min;
while(1)
{
x=p.top().second;
Min=p.top().first;
p.pop();
if(!bj[x])break;
}
bj[x]=1;
for(int i=0;i<a[x].size();i++)
{
if(!bj[a[x][i].first]&&dis[a[x][i].first]>dis[x]+a[x][i].second)
{
dis[a[x][i].first]=dis[x]+a[x][i].second;
p.push({dis[a[x][i].first],a[x][i].first});
}
}
}
for(int i=1;i<=n;i++)
{
cout<<dis[i]<<" ";
}
}
判断负环
TZOJ:8538
判断当前图内有无负环
bellman算法(优化)SPFA
通过记录更新点的个数,从而达到判断负环的方法,如果有负环的情况,就会一直更新下去,记录更新点情况,假如更新当前点时,之前的更新点已经大于等于n,那么就说明有负环并且还在更新,就可以返回true。
用队列去存储更新点,如果在队列中的就标记,之后如果有最短路被更新判断是否标记,没标记过就放进队列。
O(m~nm)
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=2e3+10;
int n,m;
int dis[N],bj[N],cnt[N];
vector<pair<int,int> >a[2000+10];
bool SPFA()
{
for(int i=1;i<=n;i++)dis[i]=1e9,cnt[i]=0,bj[i]=0;
int p[2000+10],t=0;
dis[1]=0;
bj[1]=1;
p[++t]=1;
while(t)
{
int x=p[t];
t--;
bj[x]=0;
for(int i=0;i<a[x].size();i++)
{
if(dis[a[x][i].first]>dis[x]+a[x][i].second)
{
dis[a[x][i].first]=dis[x]+a[x][i].second;
cnt[a[x][i].first]=cnt[x]+1;
if(cnt[a[x][i].first]>=n)return true;
if(!bj[a[x][i].first])p[++t]=a[x][i].first,bj[a[x][i].first]=1;
}
}
}
return false;
}
bool solve()
{
cin>>n>>m;
for(int i=1;i<=n;i++)a[i].clear();
for(int i=1;i<=m;i++)
{
int x,y,z;
cin>>x>>y>>z;
if(z>=0)
{
a[x].push_back({y,z});
a[y].push_back({x,z});
}
else a[x].push_back({y,z});
}
if(SPFA())return true;
else return false;
}
signed main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int _;
cin>>_;
while(_--)
{
if(solve())cout<<"YES"<<endl;
else cout<<"NO"<<endl;
}
}
全源最短路
TZOJ:8539
出现负环输出-1,否则就输出各个点到其他点的最短距离之和,有负权边。
Johnson算法(SPFA+dijkstra+优化)
用一个0点与其他点全部相连,其路径都是0,再用SPFA去得到0点到第i个点的最短距离h【i】,判断过程中如果有负环就可以退出。
然后我们将每个点的权边w【u,v】更新成w'【u,v】=w【u,v】+h【u】-h【v】。
这样新的边权w'【u,v】就一定是非负数,这时候就可以用dijkstra的优化走n遍得到所有点的最短路。
O(nm logm)
证明如下:
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=3e3+10;
int n,m,bj[N],dis[N],h[N],cnt[N];
vector<pair<int,int> >a[N];
queue<int>p;
priority_queue<pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > >p1;
bool SPFA()
{
for(int i=0;i<=n;i++)h[i]=1e12;
memset(bj,0,sizeof(bj));
memset(cnt,0,sizeof(cnt));
bj[0]=1;
h[0]=0;
p.push(0);
while(p.size())
{
int x=p.front();
p.pop();
bj[x]=0;
for(int i=0;i<a[x].size();i++)
{
if(h[a[x][i].first]>h[x]+a[x][i].second)
{
h[a[x][i].first]=h[x]+a[x][i].second;
cnt[a[x][i].first]=cnt[x]+1;
if(cnt[a[x][i].first]>n)
{
cout<<-1<<endl;
return true;
}
if(!bj[a[x][i].first])p.push(a[x][i].first),bj[a[x][i].first]=1;
}
}
}
return false;
}
void dijiestra(int x)
{
memset(bj,0,sizeof(bj));
for(int i=1;i<=n;i++)dis[i]=1e12;
dis[x]=0;
p1.push({dis[x],x});
while(!p1.empty())
{
int t;
while(p1.size())
{
t=p1.top().second;
p1.pop();
if(!bj[t])break;
}
bj[t]=1;
for(int i=0;i<a[t].size();i++)
{
if(!bj[a[t][i].first]&&dis[a[t][i].first]>dis[t]+a[t][i].second)
{
dis[a[t][i].first]=dis[t]+a[t][i].second;
p1.push({dis[a[t][i].first],a[t][i].first});
}
}
}
}
void solve()
{
cin>>n>>m;
for(int i=0;i<=n;i++)a[i].clear();
for(int i=1;i<=m;i++)
{
int x,y,z;
cin>>x>>y>>z;
a[x].push_back({y,z});
}
for(int i=1;i<=n;i++)a[0].push_back({i,0});
if(SPFA())return;
for(int i=1;i<=n;i++)
{
for(int j=0;j<a[i].size();j++)
{
a[i][j].second+=h[i]-h[a[i][j].first];
}
}
for(int i=1;i<=n;i++)
{
dijiestra(i);
int sum=0;
for(int j=1;j<=n;j++)
{
dis[j]=dis[j]-h[i]+h[j];
if(dis[j]>1e9)dis[j]=1e9;
sum+=dis[j]*j;
}
cout<<sum<<endl;
}
}
signed main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int _=1;
while(_--)
{
solve();
}
}
无向图的最小环问题
TZOJ:8540
求一个环的边权之和最小值,这个环内的点数要大于2。
Floyd算法(优化)
不是很懂什么原理,枚举以每个点为环的两个点,通过当前点与这两个点之间是否有相连边,以及这两个点之间的最短距离,就可以判断有无环和环的边权值之和。
O(n*n*n)
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e2+10;
int n,m,a[N][N],dis[N][N];
signed main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
cin>>n>>m;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(i!=j)a[i][j]=1e9;
for(int i=1;i<=m;i++)
{
int x,y,z;
cin>>x>>y>>z;
a[x][y]=a[y][x]=min(a[x][y],z);
}
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)dis[i][j]=a[i][j];
int Min=1e9;
for(int k=1;k<=n;k++)
{
for(int i=1;i<k;i++)
for(int j=i+1;j<k;j++)
Min=min(Min,dis[i][j]+a[k][i]+a[k][j]);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
dis[i][j]=min(dis[i][j],dis[i][k]+dis[j][k]);
}
if(Min==1e9)cout<<"No solution."<<endl;
else cout<<Min<<endl;
}