2.5.6 图的应用
例题:Roadlocks POJ No.3255
我们把路口看作顶点,把道路看作边的无向图。虽然用Dijkstra等算法可以简单地求出最短路径,但是次短路应该怎么算呢?Dijkstra地思路是一次确定尚未确定地定点中距离最小地顶点。按照这个思路对算法进行少许修改,就可以简单地求出次短路了。
到某个顶点v的次短路要么是到其他顶点u的最短路再加上u->v的边,要么是到u的次短路再加上u->v的边,因此所需要求得就是到所有顶点得最短路和次短路。
int N,R;
vector<edge> G[maxv];
int dist[maxv];
int dist2[maxv];
typedef pair<int,int> P;
void prim(){
fill(dist,dist+N,INF);
fill(dist2,dist2+N,INF);
dist[0]=[0];
priority_queue <P,vector<P>,greater<P>> que;
que.push(P(0,0));
while(!que.empty()){
P p=que.top();
que.pop();
int v=p.second,d=p.first;
if(dist2[v]<d)continue;
for(int i=0;i<G[v].size();i++){
edge e=G[v][i];
int d2=d+e.cost;
if(dist[e.to]>d2){//if能通过的d2只可能是最短路径
swap(dist[e.to],d2);
que.push(P(dist[e.to],e.to));
}
if(dist2[e.to]>d2&&d2>dist[e.to]){
dist2[e.to]=d2;
que.push(P(dist2[e.to],e.to));
}
}
}
cout << dist2[N-1]<<endl;
}
例题:Conscription POJ No.3723
让我们设想一下这样一个无向图:在征召某个人a时,如果使用了a和b之间的关系,那么就是一条从a到b的边。把人看作顶点,把关系看作边。
AC代码:
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
struct node{
int u;
int v;
int val;
bool operator < (const node & x)const{
return x.val>val;
}
}g[50005];
int N,M,R;
int par[50005],rank[50005];
int find(int x){
if(par[x]==x)return x;
return par[x]=find(par[x]);//这里之所以要用par[x]=find(par[x])是为了路径压缩
}
void unite(int x,int y){
x=find(x);
y=find(y);
if(x==y)return;
if(rank[x]<rank[y]){
par[x]=y;
}else{
par[y]=x;
if(rank[x]==rank[y])rank[x]++;
}
}
bool same(int x,int y){
return find(x)==find(y);
}
int kruskal(){
int res=0;
for(int i=0;i<R;i++){
if(!same(g[i].u,g[i].v)){
unite(g[i].u,g[i].v);
res+=g[i].val;
}
}
return res;
}
int main(){
int T;
scanf("%d",&T);
while(T--){
scanf("%d%d%d",&N,&M,&R);
int u,v,w;
for(int i=0;i<R;i++){
scanf("%d%d%d",&u,&v,&w);
g[i].u=u;
g[i].v=N+v;
g[i].val=-w;
}
sort(g,g+R);
for(int i=0;i<=N+M;i++){
par[i]=i;
rank[i]=0;
}
printf("%d\n",10000*(N+M)+kruskal());
}
return 0;
}
/*
5 5 8
4 3 6831
1 3 4583
0 0 6592
0 1 3063
3 3 4975
1 4 2049
4 2 2104
2 2 781
*/
//这里的答案时69022而不是书上的样例
例题:Layout POJ No.3169
记第i号牛的位置是d[i]。首先,牛是按照编号顺序进行排列的,所以有d[i]<=d[i+1]成立。其次,对于每队关系好的牛之间的最大距离限制,都有d[AL]+DL>=d[BL]成立。同样,对于每队关系不好的牛,都有d[AD]+DD<=d[BD]成立。因此,原问题可以转化为在满足这三类不等式的情况下,求解d的d[N]-d[1]的最大值问题。这是线性规划问题,可以使用单纯形法登较复杂的算法求解。但这道题还有更加简单的方法。
这些不等式的特点是所有的式子的两边都只出现了1个变量(这种特殊形式的不等式方程组又叫差分约束系统)。实际上,图上的最短路径问题也可以用这种形式表示出来。记从起点s出发,到各个顶点v的最短路径为d(v)。因此,对于每条权值为w的边e=(v,u),都有d(v)+w>=d(u)成立。反之,在满足全部这些约束不等式的d中,d(v)-d(s)的最大值就是从s到v的最短距离。需要注意这里不是最小值,而是最大值对应着最短距离。
把原来的问题和最短路问题进行比较就可以发现,两个问题完全一样的形式。也就是说,可以通过把原来的问题的每一个约束不等式对应成图中的一条边来构图,然后通过解决最短路问题来解决原问题。首先把顶点编号为1~N。d[i]<=d[i+1]变形成d[i+1]+0>=d[i],因此从顶点i+1向顶点i连一条权值为0的边,同样d[AL]+DL>=d[BL]对应从顶点AL向顶点BL连一条权值为DL的边,d[AD]+DD<=d[BD]对应从顶点BD向顶点AD连一条权值为-DD的边。所求问题是d[N]-d[1]的最大值,对应位顶点1到顶点N的最短距离,由于图中存在负圈便,因此不适用Dijkstra算法而是使用Bellman-Ford算法求解,时间复杂度为O(N(N+ML+MD)).
int N,ML,MD;
int AL[max_ml],BL[max_ml],DL[max_ml];
int AD[max_md],BD[max_md],DD[max_md];
int d[maxn];//最短距离
bool updated;//是否有更新
void update(int &x,int y){
if(x>y){
x=y;
updated=true;
}
}
void bellmanford(){
for(int k=0;k<=N;k++){
updated=false;
//从i+1到i的权值为0
for(int i=0;i+1<N;i++){
if(d[i+1]<INF)update(d[i],d[i+1]);
}
//从AL到BL的权值为DL
for(int i=0;i<ML;i++){
if(d[AL[i]-1]<INF)update(d[BL[i]-1],d[AL[i]-1]+DL[i]);
}
for(int i=0;i<MD;i++){
if(d[BD[i]-1]<INF)update(D[AD[i-1]],D[BD[i-1]]-DD[i]);
}
}
}
void solve(){
//检查是否有负图
fill(d,d+N,0);
bellmanford();
if(update){
cout << -1 << endl;
return;
}
fill(d,d+N,INF);
d[0]=0;
bellmanford();
int res=d[N-1];
if(res==INF)cout << -2 << endl;
else cout << res << endl;
}