kuangbin专题四 最短路练习【从入门到熟练】【13题】

【POJ 2253 Frogger】

这道题求从u到v中所有通路的最大边最小

我直接二分做了,但实际上这种题是最短路算法的变种,意义在于告诉我们spfa这些算法不仅能维护出最短路,稍加修改后可以维护出很多其他东西。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<queue>
#include<string>
#include<stack>
#include<fstream>
#include<map>
#include<iomanip> 
#include<algorithm>
#include<vector>
#define INF 2e9
#define maxnode 200000
#define ll long long
#define lowbit(x) (x&(-x))
const int mod = 10007;
const int MAXN = 2e2 + 10;
int dx[4]={0,0,1,-1};
int dy[4]={1,-1,0,0};
using namespace std;

struct node{
    int v,d;
    node(int v1,int d1): v(v1),d(d1) {}
};

vector<node> edges[MAXN];

int dist[MAXN],visit[MAXN],n;
void spfa(){
    for(int i=1;i<=n;i++) dist[i]=INF;
    dist[1]=0; visit[1]=1;
    queue<int> q;
    q.push(1);
    while( !q.empty() ){
        int u = q.front(); q.pop();
        visit[u]=0;
        for(int i=0;i<edges[u].size();i++){
            int v = edges[u][i].v;
            if( dist[v]>dist[u]+edges[u][i].d ){
                dist[v] = dist[u]+edges[u][i].d;
                if( !visit[v] ) q.push(v);
            }
        }
    }
    
}

int x[MAXN],y[MAXN];
double dis[MAXN][MAXN];

int main(){
    //ios::sync_with_stdio(false);
    int tc=0;
    while(1){
        scanf("%d",&n);
        if(n==0) break;
        for(int i=1;i<=n;i++) scanf("%d%d",x+i,y+i);
        
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
                double x1 = abs(x[i]-x[j]);
                double y1 = abs(y[i]-y[j]);
                dis[i][j] = sqrt(x1*x1 + y1*y1);
                //cout<<i<<" "<<j<<" "<<dis[i][j]<<endl;
            }
        }
        
        double start=0,end=3000,mid;
        while( end-start>=0.00001 ){
            mid = (start+end)/2;
            for(int i=1;i<=n;i++) edges[i].clear();
            
            for(int i=1;i<=n;i++){
                for(int j=1;j<=n;j++){
                    if( mid>=dis[i][j] ) edges[i].push_back( node(j,1) );
                }
            }
            
            spfa();
            if( dist[2]!=INF ) end=mid;
            else start=mid;
        }
        printf("Scenario #%d
",++tc);
        printf("Frog Distance = %.3lf
",end);
        printf("
");
    }    
    
    return 0;
}
View Code

【POJ 1797 Heavy Transportation】

维护所有通路的最小边最大

那修改后的松弛操作就是 dist[v] = max( dist[v], min( dist[u], edges[u][i].d ) )

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<queue>
#include<string>
#include<stack>
#include<fstream>
#include<map>
#include<iomanip> 
#include<algorithm>
#include<vector>
#define INF 2e9
#define maxnode 200000
#define ll long long
#define lowbit(x) (x&(-x))
const int mod = 10007;
const int MAXN = 1e3 + 10;
int dx[4]={0,0,1,-1};
int dy[4]={1,-1,0,0};
using namespace std;

struct node{
    int v,d;
    node(int v1,int d1): v(v1),d(d1) {}
};

vector<node> edges[MAXN];

int dist[MAXN],visit[MAXN],n,m;//dist[i]代表从源点到i点中所有通路的最小边最大值 
void spfa(){
    for(int i=1;i<=n;i++) dist[i]=0;
    dist[1]=INF; visit[1]=1;
    queue<int> q;
    q.push(1);
    while( !q.empty() ){
        int u = q.front(); q.pop();
        visit[u]=0;
        for(int i=0;i<edges[u].size();i++){
            int v = edges[u][i].v;
            int weight = min( edges[u][i].d,dist[u] );//weight是这条通路上的最小边 
            if( weight>dist[v] ){
                dist[v] = weight;
                if( !visit[v] ) {
                    //visit[v]=1;
                    q.push(v);
                }
            }
        }
    }
    
}

//找所有通路中最小边最大的 
int main(){
    //ios::sync_with_stdio(false);
    int t,tc=0; scanf("%d",&t); 
    while(t--){
        scanf("%d%d",&n,&m);
        for(int i=1;i<=m;i++){
            int u,v,d; scanf("%d%d%d",&u,&v,&d);
            edges[u].push_back( node(v,d) );
            edges[v].push_back( node(u,d) );
        }
        
        spfa();
        printf("Scenario #%d:
",++tc);
        printf("%d
",dist[n]);
        printf("
");
        
        for(int i=1;i<=n;i++) edges[i].clear();
    }    
    
    return 0;
}
View Code

【POJ 3268 Silver Cow Party】

想到跑flyod但n^3太慢就t了,实际上反向建边跑两边spfa就可以了!

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<queue>
#include<string>
#include<stack>
#include<fstream>
#include<map>
#include<iomanip> 
#include<algorithm>
#include<vector>
#define INF 2e9
#define maxnode 200000
#define ll long long
#define lowbit(x) (x&(-x))
const int mod = 10007;
const int MAXN = 1e3 + 10;
int dx[4]={0,0,1,-1};
int dy[4]={1,-1,0,0};
using namespace std;

struct node{
    int v,d;
    node(int v1,int d1): v(v1),d(d1) {}
};
vector<node> edges[MAXN];
int vis[MAXN],dist1[MAXN],dist2[MAXN],n,m,x;

void spfa1(){
    for(int i=1;i<=n;i++) dist1[i]=INF;
    queue<int> q;
    q.push(x); vis[x]=1; dist1[x]=0;
    while(!q.empty()){
        int u = q.front(); q.pop();
        vis[u]=0;
        for(int i=0;i<edges[u].size();i++){
            int v = edges[u][i].v;
            int d = edges[u][i].d;
            if( dist1[u]+d<dist1[v] ){
                dist1[v]=dist1[u]+d;
                if( !vis[v] ){
                    q.push(v);
                    vis[v]=1;
                }
            }
        }
    }    
}

void spfa2(){
    for(int i=1;i<=n;i++) dist2[i]=INF;
    queue<int> q;
    q.push(x); vis[x]=1; dist2[x]=0;
    while(!q.empty()){
        int u = q.front(); q.pop();
        vis[u]=0;
        for(int i=0;i<edges[u].size();i++){
            int v = edges[u][i].v;
            int d = edges[u][i].d;
            if( dist2[u]+d<dist2[v] ){
                dist2[v]=dist2[u]+d;
                if( !vis[v] ){
                    q.push(v);
                    vis[v]=1;
                }
            }
        }
    }
    
}

int u[100005],v[100005],d[100005];


int main(){
    //ios::sync_with_stdio(false);
    while( scanf("%d%d%d",&n,&m,&x)!=EOF ){
        
        for(int i=1;i<=m;i++){
            scanf("%d%d%d",u+i,v+i,d+i);
            edges[ u[i] ].push_back( node(v[i],d[i]) );
        }
        
        spfa1();
        for(int i=1;i<=n;i++) edges[i].clear();    
        for(int i=1;i<=m;i++) edges[ v[i] ].push_back( node(u[i],d[i]) );
        spfa2();
        
        int ans=-1;
        for(int i=1;i<=n;i++){//枚举所有cows
            ans = max( ans,dist1[i]+dist2[i] );
        }
        printf("%d
",ans);
        
        for(int i=1;i<=n;i++) edges[i].clear();            

    }    
    
    return 0;
}
View Code

【POJ 1860 Currency Exchange】

题目问的是找正环,那改一下dist维护的数值就行了。

但我一开始没看出来,所以写了bfs+剪枝也过了。后来我发现spfa好像也就是bfs+剪枝啊!维护的dist数组就是过程性剪枝,松弛就是剪枝的具体操作

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<queue>
#include<string>
#include<stack>
#include<fstream>
#include<map>
#include<iomanip> 
#include<algorithm>
#include<vector>
#define INF 2e9
#define maxnode 200000
#define ll long long
#define lowbit(x) (x&(-x))
const int mod = 10007;
const int MAXN = 1e2 + 10;
int dx[4]={0,0,1,-1};
int dy[4]={1,-1,0,0};
using namespace std;

struct exchange{
    int type;
    double r,c;
    exchange(int t1,double r1,double c1): type(t1),r(r1),c(c1) {}
};

vector<exchange> edges[MAXN];//edges[i]是拿着i货币能选择的一些兑换方案 

double d[MAXN],money;
int n,m,s;

struct node{
    int type;
    double m;//状态是拿着m个type钱
    node(int t1,double m1): type(t1),m(m1) {} 
};

bool bfs(){
    for(int i=1;i<=n;i++) d[i]=-INF;//最优性剪枝,d[i]为i种货币最多有多少个 
    queue<node> q;
    q.push( node(s,money) ); d[s]=money;
    while( !q.empty() ){
        node p = q.front(); q.pop();
        if( p.type==s && p.m>money ) return true;
        for(int i=0;i<edges[p.type].size();i++){//能怎么换 
            exchange e = edges[p.type][i];//在这个所换 
            double m1 = (p.m-e.c)*e.r;
            if( m1>d[ e.type ] ){
                d[e.type]=m1;
                q.push( node(e.type,m1) );
            }
        }
    }

    return false;
}

int main(){
    //ios::sync_with_stdio(false);
    while( scanf("%d%d%d%lf",&n,&m,&s,&money)!=EOF ){
        
        for(int i=1;i<=m;i++){
            int a,b; 
            double r1,c1,r2,c2; scanf("%d%d%lf%lf%lf%lf",&a,&b,&r1,&c1,&r2,&c2);
            edges[a].push_back( exchange(b,r1,c1) );
            edges[b].push_back( exchange(a,r2,c2) );
        }
        
        if( bfs() ) printf("YES
");
        else printf("NO
");
        
        for(int i=1;i<=n;i++) edges[i].clear();
    }    
    
    return 0;
}
View Code

【POJ 3660 Cow Contest】

难点在于建模,b比a弱,就建一条有向边从b到a。定义a能确定排名的意思为对于所有的其他人,要么我能到他(我比他弱),要么他能到我(我比他强),所以跑一次flyod就行了

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<queue>
#include<string>
#include<stack>
#include<fstream>
#include<map>
#include<iomanip> 
#include<algorithm>
#include<vector>
#define INF 2e9
#define maxnode 200000
#define ll long long
#define lowbit(x) (x&(-x))
const int mod = 10007;
const int MAXN = 1e2 + 10;
int dx[4]={0,0,1,-1};
int dy[4]={1,-1,0,0};
using namespace std;


ll dist[MAXN][MAXN];

int main(){
    //ios::sync_with_stdio(false);
    int n,m;
    while( scanf("%d%d",&n,&m)!=EOF ){

        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
                if( i==j ) continue;
                else dist[i][j]=INF;
            }
        }
        
        
        for(int i=1;i<=m;i++){
            int u,v; scanf("%d%d",&u,&v);
            //u比v强,从v到u有一条边
            dist[v][u]=1;
        }
        
        for(int k=1;k<=n;k++){
            for(int i=1;i<=n;i++){
                for(int j=1;j<=n;j++){
                    dist[i][j] = min( dist[i][j],dist[i][k]+dist[k][j] );
                }
            }
        }
        
        int ans=0;
        for(int i=1;i<=n;i++){
            bool flag=true;//假设排名确定 
            for(int j=1;j<=n;j++){
                if( dist[i][j]!=INF || dist[j][i]!=INF  ) continue;
                flag=false;
            }
            if( flag ) ans++;
        }
        
        printf("%d
",ans);
        
    }

    
    return 0;
}
View Code

【POJ 2240 Arbitrage】

找正环模板

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<queue>
#include<string>
#include<stack>
#include<fstream>
#include<map>
#include<iomanip> 
#include<algorithm>
#include<vector>
#define INF 2e9
#define maxnode 200000
#define ll long long
#define lowbit(x) (x&(-x))
const int mod = 10007;
const int MAXN = 1e2 + 10;
int dx[4]={0,0,1,-1};
int dy[4]={1,-1,0,0};
using namespace std;

map<string,int> mp; 
struct node{
    int v;
    double rate;
    node(int v1,double r): v(v1),rate(r) {}
};

vector<node> edges[MAXN];

double dist[MAXN];//dist[i]指的是从起点到 i 所拥有的最多i货币个数 
int n,m;
int vis[MAXN],update[MAXN];

bool spfa(){//找正环 
    memset(dist,0,sizeof(dist));
    memset(vis,0,sizeof(vis));
    memset(update,0,sizeof(update));
    
    queue<int> q;
    q.push(1); vis[1]=1; update[1]++;
    dist[1]=1;
    while( !q.empty() ){
        int u=q.front(); q.pop();
        //cout<<u<<endl;
        //for(int i=1;i<=n;i++) cout<<dist[i]<<" "; cout<<endl<<endl;
        vis[u]=0;
        for(int i=0;i<edges[u].size();i++){
            double r = edges[u][i].rate;
            int v = edges[u][i].v;
            if( dist[u]*r >= dist[v] ){
                dist[v] = dist[u]*r;
                if( !vis[v] ){
                    q.push(v);
                    vis[v]=1;
                    update[v]++;
                    if( update[v]>=n ) return true;
                }                    
    
            }        
        }
    }
    return false;
    
}

int main(){
    ios::sync_with_stdio(false);
    int tc=0;
    while(1){
        cin>>n;
        if(n==0) break;
        for(int i=1;i<=n;i++){
            string s; cin>>s;
            mp[s]=i;//s货币的id是i 
        }
        cin>>m;
        for(int i=1;i<=m;i++){
            string a,b; double rate;
            cin>>a>>rate>>b;
            edges[ mp[a] ].push_back( node(mp[b],rate) );
        }
        //Case 1: Yes
        if( spfa() ) cout<<"Case "<<++tc<<": Yes"<<endl;
        else cout<<"Case "<<++tc<<": No"<<endl;

        for(int i=1;i<=n;i++) edges[i].clear();
    }

    
    return 0;
}
View Code

【POJ 3159 Candies】

首先要建出模型题目问的是1到n的最短路(维护dist数组,dist[i]表示1号同学最多可以比i同学少多少颗糖果,然后发现松弛的方法跟最短路方法一样)

之后是卡常,通过看题解知道要用前向星+栈维护才可以过,算是体现出了spfa的不稳定性和其算法本质是搜索(模拟dfs)。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<queue>
#include<string>
#include<stack>
#include<fstream>
#include<map>
#include<iomanip> 
#include<algorithm>
#include<vector>
#define INF 2e9
#define maxnode 200000
#define ll long long
#define lowbit(x) (x&(-x))
const int mod = 10007;
const int MAXN = 3e4 + 10;
int dx[4]={0,0,1,-1};
int dy[4]={1,-1,0,0};
using namespace std;

struct Edge{
    int v,d,next;
}edges[150000];

int head[MAXN],cnt;

void add_edge(int u,int v,int d){//建一条从u到v的边 
    edges[++cnt].next = head[u];
    head[u]=cnt;
    edges[cnt].v = v; 
    edges[cnt].d = d;
}

int dist[MAXN],vis[MAXN],n,m;//dist[i]代表snoopy最多比i同学少多少颗糖果 

void spfa(){
    memset(vis,0,sizeof(vis));
    for(int i=1;i<=n;i++) dist[i]=INF;
    
    stack<int> q;
    while( !q.empty() ) q.pop();
    
    q.push(1); vis[1]=1;
    dist[1]=0;
    while( !q.empty() ){
        int u=q.top(); q.pop();
        vis[u]=0;
        for(int i=head[u];i;i=edges[i].next){
            int d = edges[i].d;
            int v = edges[i].v;
            if( dist[v]>dist[u]+d ){
                dist[v] = dist[u]+d;
                if( !vis[v] ){
                    q.push(v);
                    vis[v]=1;
                }                    
            }        
        }
    }

}

int main(){
    //ios::sync_with_stdio(false);
    
    while( scanf("%d%d",&n,&m)!=EOF ){
        
        cnt=0;
        memset(head,0,sizeof(head));
        
        for(int i=1;i<=m;i++){
            int u,v,d; scanf("%d%d%d",&u,&v,&d);
            add_edge(u,v,d);
        }
        
        spfa();
        printf("%d
",dist[n]);
    }

    
    return 0;
}
View Code

【POJ 2502 Subway】

根据题意建图就行,输入比题难写。poj里g++会wa,但c++就过了

#include<iostream>
#include<cstdio>
#include<cstring>
#include<math.h>
#include<queue>
#include<string>
#include<stack>
#include<fstream>
#include<map>
#include<iomanip> 
#include<algorithm>
#include<vector>
#define INF 2e9
#define maxnode 200000
#define ll long long
#define lowbit(x) (x&(-x))
const int mod = 10007;
const int MAXN = 2e2 + 10;
int dx[4]={0,0,1,-1};
int dy[4]={1,-1,0,0};
using namespace std;

struct stop{
    int x,y;
    stop(int x1=0,int y1=0):x(x1),y(y1) {}
}stops[MAXN];

vector<stop> line;

struct edge{
    int v,next;//next指的是相同u情况下的下一条边 
    double d;
}edges[100000];
int head[MAXN],cnt;

void add_edge(int u,int v,double d){
    edges[++cnt].next=head[u];
    edges[cnt].v=v;
    edges[cnt].d=d;
    head[u] = cnt;
}

double dist[MAXN];
int vis[MAXN];

void spfa(){
    for(int i=1;i<MAXN;i++) dist[i]=INF;
    queue<int> q;
    q.push(1); vis[1]=1; dist[1]=0;
    while( !q.empty() ){
        int u = q.front(); q.pop();
        vis[u]=0;
        for(int i=head[u];i;i=edges[i].next){
            int v = edges[i].v;
            double d = edges[i].d;
            if( dist[v]>dist[u]+d ){
                dist[v] = dist[u]+d;
                if( !vis[v] ){
                    vis[v]=1;
                    q.push(v);
                }
            }
        }

    }
    
}

int main(){
    //ios::sync_with_stdio(false);
    //freopen("1.in","r",stdin);
    //freopen("1.out","w",stdout);
    //1是起点,2是终点 
    int x1,y1,x2,y2;
    scanf("%d%d%d%d",&x1,&y1,&x2,&y2 );
    
    stops[1] = stop(x1,y1);
    stops[2] = stop(x2,y2);
    
    int xcha = abs(x1-x2);
    int ycha = abs(y1-y2);
    double da = sqrt( 1.0*xcha*xcha+1.0*ycha*ycha )/10000.0*60;
    add_edge(1,2,da);
    add_edge(2,1,da);
    
    int id=2,num=0;
    while( scanf("%d%d",&x1,&y1)!=EOF ){
        if( x1==-1 && y1==-1 ) num=0;//目前lines上有几个 
        else{
            num++; id++;//目前stop的id
            stops[id]=stop(x1,y1);
            //将它与lines上的上一个stop建双向边,与所有不在lines上的其他stop建双向边
            if( num>1 ){
                for(int i=1;i<id-1;i++){
                    int x = abs(stops[i].x-x1);
                    int y = abs(stops[i].y-y1);
                    double dis = sqrt( 1.0*x*x+1.0*y*y )/10000.0*60;
                    add_edge(i,id,dis);
                    add_edge(id,i,dis); 
                }
                
                int x = abs(stops[id-1].x-x1);
                int y = abs(stops[id-1].y-y1);
                double dis = sqrt( 1.0*x*x+1.0*y*y )/40000.0*60;
                add_edge(id-1,id,dis);
                add_edge(id,id-1,dis);
            }
            else{
                for(int i=1;i<id;i++){
                    int x = abs(stops[i].x-x1);
                    int y = abs(stops[i].y-y1);
                    double dis = sqrt( 1.0*x*x+1.0*y*y )/10000.0*60;
                    add_edge(i,id,dis);
                    add_edge(id,i,dis); 
                }
            } 
             
        }
    }
    spfa();
    printf("%.0lf
",dist[2] );    
    
    return 0;
}
View Code

【POJ 1062 昂贵的婚礼】

真的是什么算法都能跟暴力扯上关系。。以后要多长个心眼,就像二分一样,枚举也是ACM常用的套路一种啊!

逐渐的建图有点感觉了,这题的难点在于解决【等级限制】的问题。kuangbin专题里的分水槛的题终于来了

我一开始想的是改变边的构造,想的是如果某某交易发生了,那就自动有一些边之后不能再走。这个感觉是对的,但没办法实现。实际上可以从点出发去考虑,考虑哪些点会在这个图里出现,那么等级差不超过M的点会在图里出现。再加上已知level[1](酋长的等级)必须在图里出现,我们从[ level[1]-m, level[1] ], [ level[1]-m+1, level[1]+1 ] , ... 枚举到 [ level[1] , level[1]+M ]就行了。那么每次找最短路就只考虑主人等级落在这个区间里的点

#include<iostream>
#include<cstdio>
#include<cstring>
#include<math.h>
#include<queue>
#include<string>
#include<stack>
#include<fstream>
#include<map>
#include<iomanip> 
#include<algorithm>
#include<vector>
#define INF 2e9
#define maxnode 200000
#define ll long long
#define lowbit(x) (x&(-x))
const int mod = 10007;
const int MAXN = 1e2 + 10;
int dx[4]={0,0,1,-1};
int dy[4]={1,-1,0,0};
using namespace std;

struct edge{
    int v,next;//next指的是相同u情况下的下一条边 
    int d;
}edges[100000];

int head[MAXN],cnt;

void add_edge(int u,int v,int d){
    edges[++cnt].next=head[u];
    edges[cnt].v=v;
    edges[cnt].d=d;
    head[u] = cnt;
}

int dist[MAXN],vis[MAXN],include[MAXN],n,m;

void spfa(){
    for(int i=1;i<=n;i++) dist[i]=INF;
    queue<int> q;
    q.push(0); vis[0]=1; dist[0]=0;
    while( !q.empty() ){
        int u = q.front(); q.pop();
        vis[u]=0;
        for(int i=head[u];i;i=edges[i].next){
            int v = edges[i].v;
            int d = edges[i].d;
            if( dist[v]>dist[u]+d && include[v] ){
                dist[v] = dist[u]+d;
                if( !vis[v] ){
                    vis[v]=1;
                    q.push(v);
                }
            }
        }

    } 
}

int level[MAXN];

int main(){
    //ios::sync_with_stdio(false);
    //freopen("1.in","r",stdin);
    //freopen("1.out","w",stdout);
    //0是起点 代表什么都没有 
    //1是终点 代表有了聘礼 
    while( scanf("%d%d",&m,&n)!=EOF ){
        for(int i=1;i<=n;i++){
            int P,L,X; scanf("%d%d%d",&P,level+i,&X);
            add_edge(0,i,P);//不用优惠政策楞头买 
            for(int j=1;j<=X;j++){//X个替代品 
                int t,v; scanf("%d%d",&t,&v);
                add_edge(t,i,v);//可以花v的钱,从拥有t变成拥有i 
            }
        }
        
        int ans=INF;
        for(int i=level[1]-m;i<=level[1];i++){
            for(int j=1;j<=n;j++){
                if( level[j]<i || level[j]>i+m ) include[j]=0;//j结点不考虑在图里面 
                else include[j]=1;     
            }
            spfa();
            ans=min(ans,dist[1]);
        }
        printf("%d
",ans);
        cnt=0;
    }
    
    
    return 0;
}
View Code

【HDU 4725 The Shortest Path in Nya Graph】

我一开始直接耿直建图了,开个vector每层的结点往上下两层建边,那直接边的数量变成n^2级别的了,所以tle。要建图优化

就是每层抽象成一个结点,这样一共有2*n个结点,其中1-n代表的是层结点,n+1-n+n代表的才是正常的结点。

那建图就是每层层结点之间建边代价c(双向边所以共2*n条)

此外再由于【每层的结点是汇点】(指的是层结点的入度只能由非本层的结点指过来),所以向本层的所有其他结点建单向代价为0的边(共n条)

然后原本的结点要向上一层或下一层的层结点建代价为c的边(共2*n条,严格点第一层和最后一层单独考虑,但无所谓了)

所以这么下来5*n级别的边就建完图了,稠密图优化成稀疏图!

还有用spfa的话【建图的顺序】会被卡!如果最后建层结点之间的边会tle,但先建就能ac。玄学。。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<math.h>
#include<queue>
#include<string>
#include<stack>
#include<fstream>
#include<map>
#include<iomanip> 
#include<algorithm>
#include<vector>
#define INF 2e9
#define maxnode 200000
#define ll long long
#define lowbit(x) (x&(-x))
const int mod = 10007;
const int MAXN = 2e5 + 10;
int dx[4]={0,0,1,-1};
int dy[4]={1,-1,0,0};
using namespace std;

struct edge{
    int v,next;//next指的是相同u情况下的下一条边 
    int d;
}edges[10*MAXN];

int head[MAXN],cnt,a[MAXN];

void add_edge(int u,int v,int d){
    edges[++cnt].next=head[u];
    edges[cnt].v=v;
    edges[cnt].d=d;
    head[u] = cnt;
}

int dist[MAXN],vis[MAXN],n,m,c,layer[MAXN];

void spfa(){
    for(int i=1;i<=2*n;i++) dist[i]=INF;
    memset(vis,0,sizeof(vis));
    
    queue<int> q;
    q.push(n+1); vis[n+1]=1; dist[n+1]=0;
    while( !q.empty() ){
        int u = q.front(); q.pop();
        vis[u]=0;
        for(int i=head[u];i;i=edges[i].next){
            int v = edges[i].v;
            int d = edges[i].d;
            if( dist[v]>dist[u]+d ){
                dist[v] = dist[u]+d;
                if( !vis[v] ){
                    vis[v]=1;
                    q.push(v);                
                }
            }
        }

    } 
}

int flag[MAXN];

int main(){    
    //ios::sync_with_stdio(false);
    //freopen("1.in","r",stdin);
    //freopen("1.out","w",stdout);
    int t,tc=0; scanf("%d",&t);
    
    //1到n是layor结点,n+1到n+n才是其他结点
    while( t-- ){
        cnt=0;
        memset(head,0,sizeof(head));
        memset(flag,0,sizeof(flag));
        
        scanf("%d%d%d",&n,&m,&c);
        for(int i=1;i<=n;i++){//2*n
            scanf("%d",layer+i);
            flag[ layer[i] ]=1;
        }
        
        
        //这两个建边交换顺序的话会tle。。。 
        for(int i=1;i<n;i++){
            if( flag[i] && flag[i+1] ){
                add_edge(i,i+1,c);
                add_edge(i+1,i,c);
            }
        } 
        
        for(int i=1;i<=n;i++){            
            add_edge(layer[i],n+i,0);
            if( layer[i]==1 ) add_edge(n+i,2,c);
            else if( layer[i]==n ) add_edge(n+i,n-1,c);
            else{
                add_edge(n+i,layer[i]+1,c);
                add_edge(n+i,layer[i]-1,c);
            }
        }
        //
        
        for(int i=1;i<=m;i++){
            int u,v,w; scanf("%d%d%d",&u,&v,&w);
            add_edge(u+n,v+n,w);
            add_edge(v+n,u+n,w);
        }
        
        spfa();

        if( dist[n+n]==INF ) dist[n+n]=-1;
        printf("Case #%d: %d
",++tc,dist[n+n]);
    }
    
    
    return 0;
}
View Code

【HDU 3416 Marriage Match IV】

被搞自闭。。

但做这一题的收获真的大,练习了写dij想明白复杂度是m+vlogv,相比而言spfa是km,所以相较之下还是dij最稳,其实做了上一题也是让我正视了spfa,将其打下神坛。

然后是会了生成一个最短路图,使得原图的所有最短路都包含在其中。也就是满足dist1[u] + c + dist2[v] == dist1[B]的边在最短路上(因为如果一条边在最短路上,那该最短路一定有 A->u + u->v + v->B的形式。)

接下来跑最大流就好了,写最大流的时候发现了一个问题,一是拿dfs,bfs写的话会比较好;因为拿stack模拟dfs写的话,若用邻接表增广的时候比较麻烦(因为你不知道用的是哪条边,开个vector不断记录就不优雅);若用邻接矩阵的会费用流重边就没法做了。所以拿dfs+bfs最好。其次学会了炸点优化也觉得挺妙的,具体实现的时候由于理解不深还遇到并解决了一个问题。

就是如果在这里写edges[i].cap的话会tle,而且会导致死循环而不是常数上的变慢,为什么呢?

因为如果这条边的cap<0的话,仅仅代表的是这一个弧用不了,那next的可能还可以啊,所以不能退出循环。会死循环是因为bfs的时候发现能到汇点,但dfs的时候又有的边走不到所以走不到汇点增广为0,因此就卡在那了。

#include<iostream>
#include<cstring>
#include<queue>
#define inf 2e9
#define maxnode 200000
#define ll long long
#define lowbit(x) (x&(-x))
const int mod = 10007;
const int maxn = 2e3 + 10;
const int maxm = 2e5 + 10;
int dx[4]={0,0,1,-1};
int dy[4]={1,-1,0,0};
using namespace std;

int n,m,cnt,head[maxn],s,t;
int dist1[maxn],dist2[maxn],vis[maxn];
struct edge{
    int v,next,d;
}edges[maxm];

void addedge(int u,int v,int d){
    cnt++;
    edges[cnt].v=v;
    edges[cnt].d=d;
    edges[cnt].next=head[u];
    head[u]=cnt;
}

struct node{
    int v,dis;
    node(int v1,int d1): v(v1),dis(d1) {}
    bool operator > (const node &p)const{
        return dis>p.dis;
    }
};

void dij1(){    
    for(int i=1;i<=n;i++) dist1[i]=inf;
    memset(vis,0,sizeof(vis));
    
    priority_queue<node,vector<node>,greater<node> > q; while(!q.empty()) q.pop();
    q.push(node(s,0)); dist1[s]=0;
    while( !q.empty() ){
        node u=q.top(); q.pop();
        if( vis[u.v] ) continue;
        vis[u.v]=1;
        for(int i=head[u.v];i;i=edges[i].next){
            int v=edges[i].v;
            if( dist1[v]>dist1[u.v]+edges[i].d ){
                dist1[v]=dist1[u.v]+edges[i].d;
                q.push(node(v,dist1[v]));
            }
        }
    }
}

void dij2(){    
    for(int i=1;i<=n;i++) dist2[i]=inf;
    memset(vis,0,sizeof(vis));
    
    priority_queue<node,vector<node>,greater<node> > q; while(!q.empty()) q.pop();
    q.push(node(t,0)); dist2[t]=0;
    while( !q.empty() ){
        node u=q.top(); q.pop();
        if( vis[u.v] ) continue;
        vis[u.v]=1;
        for(int i=head[u.v];i;i=edges[i].next){
            int v=edges[i].v;
            if( dist2[v]>dist2[u.v]+edges[i].d ){
                dist2[v]=dist2[u.v]+edges[i].d;
                q.push(node(v,dist2[v]));
            }
        }
    }
}

int u[maxm],v[maxm],c[maxm];

struct Edge{
    int v,next,cap,reverse;
}Edges[2*maxm];

void add_edge(int u,int v,int cap){
    cnt++;
    Edges[cnt].v=v;
    Edges[cnt].cap=cap;
    Edges[cnt].next=head[u];
    Edges[cnt].reverse=cnt+1;
    head[u]=cnt;
    cnt++;
    Edges[cnt].v=u;
    Edges[cnt].cap=0;
    Edges[cnt].next=head[v];
    Edges[cnt].reverse=cnt-1;
    head[v]=cnt;
}

int layor[maxn];
bool bfs(){
    memset(layor,-1,sizeof(layor));
    queue<int> q; while(!q.empty()) q.pop();
    q.push(s); layor[s]=0;
    while( !q.empty() ){
        int u=q.front(); q.pop();
        for(int i=head[u];i;i=Edges[i].next){
            int v=Edges[i].v;
            if( layor[v]==-1 && Edges[i].cap ){
                layor[v]=layor[u]+1;
                if( v==t )return true;
                else q.push(v);
            }
        }
    }
    return false;
}

int dfs(int u,int lim){//lim是目前的最大流量,返回要流多少
    if( u==t || lim==0 ) return lim;
    int flow=0;//目前要流0
    for(int i=head[u];i;i=Edges[i].next){
        Edge &e = Edges[i];
        if( layor[e.v]==layor[u]+1 && Edges[i].cap ){
            int x=dfs(e.v,min(lim,e.cap));
            lim-=x; flow+=x;
            e.cap-=x; Edges[e.reverse].cap+=x;
        }
    }
    if( !flow ) layor[u]=-2;
    return flow;
} 

int maxflow(){
    int ans=0;
    while( bfs() ) ans+=dfs(s,inf);
    return ans;
}


int main(){
    int T; scanf("%d",&T);
    while( T-- ){
        scanf("%d%d",&n,&m);
        
        cnt=0; 
        memset(head,0,sizeof(head));
        for(int i=1;i<=m;i++){
            scanf("%d%d%d",u+i,v+i,c+i);
            addedge(u[i],v[i],c[i]); 
        }
        scanf("%d%d",&s,&t);

        dij1();
        cnt=0; memset(head,0,sizeof(head));
        for(int i=1;i<=m;i++) addedge(v[i],u[i],c[i]); 
        dij2();
        
        cnt=0; memset(head,0,sizeof(head));
        for(int i=1;i<=m;i++){
            // s->u[i] , u[i]->v[i] , v[i]->t
            if( dist1[u[i]]+c[i]+dist2[v[i]]==dist1[t] ) add_edge(u[i],v[i],1);
        }
        printf("%d
",maxflow());
    }
    
    return 0;
}
View Code

【POJ 4370 0 or 1】

智商题啊。。

考虑x矩阵中x[i][j]=1在图里面的意义是i和j之间的边被走,那么x矩阵里的第i行代表第i个点的出度情况,第i列代表第i个点的入度情况:

1.第一行除第一个外有且有一个1 ==> 所以1只能出去一次,1号节点出度为1

2.第n列除最后一个外有且有一个1==>所以n只能被其他点到一次,n号结点入度为1

3. 第2-n-1行的和等于其列的和 ==> 2-n-1号结点出度等于入度

除了最短路外,还要想到包含1的最小环+包含n的最小环也是满足上述情况的。这样才做完

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<queue>
#include<string>
#include<stack>
#include<fstream>
#include<map>
#include<iomanip> 
#include<algorithm>
#include<vector>
#define inf 2e9
#define maxnode 200000
#define ll long long
#define lowbit(x) (x&(-x))
const int mod = 10007;
const int maxn = 3e2 + 10;
int dx[4]={0,0,1,-1};
int dy[4]={1,-1,0,0};
using namespace std;

int n;
int G[305][305];

int vis[maxn],dist[maxn];
int spfa(int s,int e){
    memset(vis,0,sizeof(vis));
    for(int i=1;i<=n;i++) dist[i]=inf;
    
    queue<int> q;
    if( s==e ){//找最小环 
        for(int i=1;i<=n;i++){
            if( i!=s ){
                q.push(i);
                vis[i]=1;
                dist[i]=G[s][i];
            }
        }
    }
    else{
        q.push(s);
        vis[s]=1; dist[s]=0;
    }

    while(!q.empty()){
        int u=q.front(); q.pop();
        vis[u]=0;
        for(int i=1;i<=n;i++){
            if( dist[i]>dist[u]+G[u][i] ){
                dist[i]=dist[u]+G[u][i];
                if( !vis[i] ){
                    q.push(i);
                    vis[i]=1;
                }
            }
        }
    }
    return dist[e];
} 

int main(){
    
    while( scanf("%d",&n)!=EOF ){
        
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
                scanf("%d",&G[i][j]); 
            }
        }
        
        printf("%d
",min(spfa(1,n),spfa(1,1)+spfa(n,n)));
    }
    
    return 0;
}
View Code

【POJ 3169 Layout】

这题跟之前的candies有点像,也是差分约束。

首先看它有两个约束条件ML和MD,那就很烦,我们可以一个一个考虑。ML代表的是A,B最远离多少,那么如果只有ML这一个约束的话,题目就跟candies一模一样了;这个时候再考虑MD,表示A,B最近隔多远;由于我们想维护的数组dist[i]代表1与i之间的最远距离,然后如果原来知道了dist[B]的话,那么我们可以推出来1到A的最远距离是dist[B]-D,所以从B到A建一条权值为-D的边就行了。

最后考虑如果有负环的话那就是no possible layout,因为负环代表有一个点dist[i]<0,现实意义是离1最远差一个负数,明显说不通;

如果没有路到dist[n]那就是多远都可以,因为没有有效约束能约束到终点。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<queue>
#include<string>
#include<stack>
#include<fstream>
#include<map>
#include<iomanip> 
#include<algorithm>
#include<vector>
#define inf 2e9
#define maxnode 200000
#define ll long long
#define lowbit(x) (x&(-x))
const int mod = 10007;
const int maxn = 1e3 + 10;
int dx[4]={0,0,1,-1};
int dy[4]={1,-1,0,0};
using namespace std;

int n,m1,m2,cnt,head[maxn],s,t;
struct edge{
    int v,next,d;
}edges[30010];

void addedge(int u,int v,int d){
    cnt++;
    edges[cnt].v=v;
    edges[cnt].next=head[u];
    edges[cnt].d=d;
    head[u]=cnt;
}

int vis[maxn],dist[maxn],update[maxn];
bool spfa(){
    memset(vis,0,sizeof(vis));
    for(int i=1;i<=n;i++) dist[i]=inf;
    memset(update,0,sizeof(update));
    
    queue<int> q;
    q.push(s); vis[s]=1; dist[s]=0;
    while(!q.empty()){
        int u=q.front(); q.pop();
        vis[u]=0;
        for(int i=head[u];i;i=edges[i].next){
            int v=edges[i].v;
            if( dist[v]>dist[u]+edges[i].d ){
                dist[v]=dist[u]+edges[i].d;
                if( !vis[v] ){
                    q.push(v);
                    update[v]++;
                    if( update[v]>=n ) return true;
                }
            }
        }
    }
    return false;
} 

int main(){
    
    while( scanf("%d%d%d",&n,&m1,&m2)!=EOF ){
        cnt=0; 
        memset(head,0,sizeof(head));
        int u,v,c; 
        
        for(int i=1;i<=m1;i++){
            scanf("%d%d%d",&u,&v,&c);
            addedge(u,v,c); 
        }
        for(int i=1;i<=m2;i++){
            scanf("%d%d%d",&u,&v,&c);
            addedge(v,u,-c);
        }
        
        s=1; t=n;
        //有负环-1,到不了t -2,其他dist[t] 
        if( spfa() ) printf("-1
");
        else if( dist[t]==inf ) printf("-2
");
        else printf("%d
",dist[t]);
        
        
    }
    
    return 0;
}
View Code

 最后总结一下最短路专题,感觉其本质是dfs+剪枝,因为松弛时用的dist数组实际上就是过程性剪枝的一种。也正因为如此,所以spfa,dij模板可以维护出除最短路外的很多东西。由于spfa很方便,在我心目中前面的题把spfa送上神坛,但由于该算法的不稳定,后面的题又把其打下神坛。。。然后是最短路的一些变种,如分层/等级限制/差分。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<queue>
#include<string>
#include<stack>
#include<fstream>
#include<map>
#include<iomanip> 
#include<algorithm>
#include<vector>
#define inf 2e9
#define maxnode 200000
#define ll long long
#define lowbit(x) (x&(-x))
const int mod = 10007;
const int maxn = 1e3 + 10;
int dx[4]={0,0,1,-1};
int dy[4]={1,-1,0,0};
using namespace std;

int n,m1,m2,cnt,head[maxn],s,t;
struct edge{
    int v,next,d;
}edges[30010];

void addedge(int u,int v,int d){
    cnt++;
    edges[cnt].v=v;
    edges[cnt].next=head[u];
    edges[cnt].d=d;
    head[u]=cnt;
}

int vis[maxn],dist[maxn],update[maxn];
bool spfa(){
    memset(vis,0,sizeof(vis));
    for(int i=1;i<=n;i++) dist[i]=inf;
    memset(update,0,sizeof(update));
    
    queue<int> q;
    q.push(s); vis[s]=1; dist[s]=0;
    while(!q.empty()){
        int u=q.front(); q.pop();
        vis[u]=0;
        for(int i=head[u];i;i=edges[i].next){
            int v=edges[i].v;
            if( dist[v]>dist[u]+edges[i].d ){
                dist[v]=dist[u]+edges[i].d;
                if( !vis[v] ){
                    q.push(v);
                    update[v]++;
                    if( update[v]>=n ) return true;
                }
            }
        }
    }
    return false;
} 

int main(){
    
    while( scanf("%d%d%d",&n,&m1,&m2)!=EOF ){
        cnt=0; 
        memset(head,0,sizeof(head));
        int u,v,c; 
        
        for(int i=1;i<=m1;i++){
            scanf("%d%d%d",&u,&v,&c);
            addedge(u,v,c); 
        }
        for(int i=1;i<=m2;i++){
            scanf("%d%d%d",&u,&v,&c);
            addedge(v,u,-c);
        }
        
        s=1; t=n;
        //有负环-1,到不了t -2,其他dist[t] 
        if( spfa() ) printf("-1
");
        else if( dist[t]==inf ) printf("-2
");
        else printf("%d
",dist[t]);
        
        
    }
    
    r
原文地址:https://www.cnblogs.com/ZhenghangHu/p/9869298.html