zoukankan      html  css  js  c++  java
  • 最短路径题目

    1797 Heavy Transportation.

    //是最短路径的变形:每条路上的是容量,需要对dijkstra进行变形
    //现在是:比较路径上载重量的大小,取小者,在所有的小的情况中取一个大的继续拓展

    #include<iostream>
    #include<cstring>
    #include<cmath>
    #include<algorithm>
    #include<stack>
    #include<cstdio>
    #include<queue>
    #include<map>
    #include<vector>
    #include<set>
    using namespace std;
    const int maxn=1010;
    const int INF=0x3fffffff;
    typedef long long LL;
    //是最短路径的变形:每条路上的是容量,需要对dijkstra进行变形
    //现在是:比较路径上载重量的大小,取小者,在所有的小的情况中取一个大的继续拓展
    int n,m;
    int dp[1010];
    int vis[1010];
    int mp[maxn][maxn];
    void dij(int st){
    	memset(vis,0,sizeof(vis[0])*(n+3));
    	for(int i=1;i<=n;i++) dp[i]=mp[st][i];  //初始能够的容量
    	dp[st]=1000000;
    	vis[st]=1;
    	for(int i=1;i<n;i++){
    		int u=st,temp=0; ///求最大
    		for(int j=1;j<=n;j++){
    			if(!vis[j]&&temp<=dp[j]){  //找到最大的 
    				temp=dp[j];
    				u=j;
    			}
    		}
    		vis[u]=1;
    		if(dp[n]) return;
    		for(int j=1;j<=n;j++){
    			if(!vis[j]){
    				dp[j]=max(dp[j],min(dp[u],mp[u][j])); //!!!!!!!!!注意这个 
    				//一种是不走u
    				//一种是走u,但是这样就有限制,所以取小的 
    			}
    		}
    	} 
    } 
    int main(){
    	int t;
    	int ca=1;
    	scanf("%d",&t);
    	while(t--){
    		scanf("%d %d",&n,&m);
    		for(int i=1;i<=n;i++){
    			memset(mp[i],0,sizeof(mp[i][0])*(n+3));
    		}
    		for(int i=0;i<m;i++){
    			int x,y,wei;
    			scanf("%d %d %d",&x,&y,&wei);
    			mp[x][y]=mp[y][x]=max(wei,mp[x][y]);
    		}
    		dij(1);
    		printf("Scenario #%d:
    ",ca++);
    		printf("%d
    
    ",dp[n]);
    	}
    return 0;
    }

    1494:【例 1】Sightseeing Trip

     

     无向图最小环问题,用Floyd求

    因为Floyd是按照结点的顺序更新最短路的,所以我们在更新最短路之前先找到一个连接点k,当前的点k肯定不存在于已存在的最短路f[i][j]的路径上,因为我们还没用这个k去更新最短路,
    相当于 (i -> k -> j -> j到i的最短路 -> i)这样一个环就找到了,接下来我们要记录路径,用path[i][j]表示在最短路i到j的路径上j的前一个结点,所以我们在更新最短路时也要更新这个点,
    原来的最短路是i -> j,现在变成了 i -> k -> j,所以有per[i][j] = pre[k][j],因为要找最小环,所以不断更新找到环的权值,环更新一次,路径也要更新一次,
    路径更新时根据pre数组迭代一下就ok了

    具体讲解:https://www.cnblogs.com/Yz81128/archive/2012/08/15/2640940.html#undefined
    https://www.cnblogs.com/qwerta/p/9833391.html

    #include<iostream>
    #include<cstring>
    #include<cmath>
    #include<algorithm>
    #include<stack>
    #include<cstdio>
    #include<queue>
    #include<map>
    #include<vector>
    #include<set>
    using namespace std;
    const int maxn=310;
    const int INF= 0x3f3f3f3f;
    typedef long long LL;
    //无向图最小环问题
    //用Floyd求
    /*
    因为Floyd是按照结点的顺序更新最短路的,所以我们在更新最短路之前先找到一个连接点k,当前的点k肯定不存在于已存在的最短路f[i][j]的路径上,因为我们还没用这个k去更新最短路,
    相当于 (i -> k -> j -> j到i的最短路 -> i)这样一个环就找到了,接下来我们要记录路径,用path[i][j]表示在最短路i到j的路径上j的前一个结点,所以我们在更新最短路时也要更新这个点,
    原来的最短路是i -> j,现在变成了 i -> k -> j,所以有per[i][j] = pre[k][j],因为要找最小环,所以不断更新找到环的权值,环更新一次,路径也要更新一次,
    路径更新时根据pre数组迭代一下就ok了
    */ 
    //具体讲解:https://www.cnblogs.com/Yz81128/archive/2012/08/15/2640940.html#undefined
    //https://www.cnblogs.com/qwerta/p/9833391.html
    int n,m,ans=INF;
    int g[maxn][maxn];
    int dis[maxn][maxn];
    int pos[maxn][maxn];  //这个是放置中间节点的
    vector<int> path;  //存环
    void rad(){
    	memset(g,0x3f,sizeof(g));
    	scanf("%d %d",&n,&m);
    	for(int i=1;i<=n;i++) g[i][i]=0;
    	for(int i=1;i<=m;i++){
    		int x,y,z;
    		scanf("%d %d %d",&x,&y,&z);
    		g[x][y]=g[y][x]=z;
    	}
    	memcpy(dis,g,sizeof(g));
    } 
    void get_path(int i,int j){
    	if(pos[i][j]==0) return;
    	int k=pos[i][j];//k保存的只是i,j的中间节点,递归插入
    	get_path(i,k);    //递归!!!
    	path.push_back(k);
    	get_path(k,j); 
    }
    void floyd(){
    	for(int k=1;k<=n;k++){   //k还没有用 
    	for(int i=1;i<k;i++){
    		for(int j=i+1;j<k;j++){
    			if(ans>(LL)(dis[i][j]+g[j][k]+g[k][i])){
    				ans=dis[i][j]+g[j][k]+g[k][i];  //找到了最小环
    				path.clear();  //就可以清空路径,因为有更好的了
    				path.push_back(i);
    				get_path(i,j);
    				path.push_back(j);
    				path.push_back(k);  //后面的两个自己放进去 
    			}
    		}
    	}	
    	for(int i=1;i<=n;i++){   //用k来更新,就是普通的floyd算法了,顺便保存一下Pos数组 
    		for(int j=1;j<=n;j++){
    			if(dis[i][j]>dis[i][k]+dis[k][j]){
    				dis[i][j]=dis[i][k]+dis[k][j];
    				pos[i][j]=k;
    			}
    		}
    	} 
    }
    } 
    
    
    int main(){
    	rad();
    	floyd();
    	if(ans==INF){   //如果没有找到环 
    		printf("No solution.");
    		return 0;
    	} 
    	for(int i=0;i<path.size();i++) printf("%d ",path[i]);
    return 0;
    }

    1495:【例 2】孤岛营救问题

     第一种方法: 这道题大部分是要BFS+状态压缩去做,这样也更快

    但是看题解说可以用分层最短路做。以获取钥匙的状态建立分层图,然后BFS就行了

    https://blog.csdn.net/a_pathfinder/article/details/100537489  里面写了BFS+状压  和 最短路得解法 

    like  汽车加油行驶问题(另一个分层图的问题)

    //这道题大部分是要BFS+状态压缩去做,这样也更快
    //但是看题解说可以用分层最短路做。以获取钥匙的状态建立分层图,然后BFS就行了
    //(感觉就是状元+bfs
    //https://blog.csdn.net/a_pathfinder/article/details/100537489  里面写了BFS+状压  和 最短路得解法 
    //like  汽车加油行驶问题
    //我又再次愤怒!!!!我到底哪里写错了
    #include<bits/stdc++.h>
    using namespace std;
    const int N = 12;
    int dir[5][3]={{0,1},{1,0},{0,-1},{-1,0}};
    int e[N][N][N][N],key[N][N][N],dkey[N][N];
    int vis[N][N][1<<14];
    int n,m;
    struct node{
    	int x,y,k,d;
    	node(){}
    	node(int x,int y,int k,int d):x(x),y(y),k(k),d(d){}
    };
    int getkey(int x,int y){
    	int ans = 0;
    	for(int i=1;i<=dkey[x][y];i++)
    	ans|=(1<<(key[x][y][i]-1));
    	return ans;
    }
    int bfs(){
    	queue<node> q;
    	int sk = getkey(1,1);
    	q.push(node(1,1,sk,0)),vis[1][1][sk] = 1;
    	while(q.size()){
    		node u = q.front();q.pop();
    		if(u.x==n && u.y==m) return u.d;
    		int ux = u.x,uy = u.y;
    		for(int i=0;i<4;i++){
    			int nx = ux+dir[i][0];
    			int ny = uy+dir[i][1];
    			int opt = e[ux][uy][nx][ny];
    			if(nx <1||ny<1||nx>n||ny>m||opt<0||(opt&&!(u.k&(1<<(opt-1))))) continue;
    			int nkey = u.k|getkey(nx,ny);
    			if(vis[nx][ny][nkey]) continue;
    			q.push(node(nx,ny,nkey,u.d+1));
    			vis[nx][ny][nkey] = 1;
    		}
    	}
    	return -1;
    }
    void read(){
    	int k,s,r;
    	scanf("%d%d%d",&n,&m,&r);
    	for(scanf("%d",&k);k--;){
    		int x1,y1,x2,y2,g;
            scanf("%d%d%d%d%d",&x1,&y1,&x2,&y2,&g);
            if(g) e[x1][y1][x2][y2]=e[x2][y2][x1][y1]=g;
            else e[x1][y1][x2][y2]=e[x2][y2][x1][y1]=-1;
    	}
       for(scanf("%d",&s);s--;) {
            int x,y,q;
            scanf("%d%d%d",&x,&y,&q);
            key[x][y][++dkey[x][y]]=q;
        }
    } 
    int main(){
    	read();
        printf("%d
    ",bfs());
    	return 0;
    }
    

    第二种方法:最短路,分层求最短路 

    们要先算出最多边数:1>>10*10*10=1024000;
    把原来矩阵每个坐标存成一个点,再对可到邻边赋权值1,在建图的时候,我们只要连接一个双向边即可,假设j是从i左移一格,那么i就是j右移一格。上下同理。
    值得注意的是,我们每次在一层的时候,在没有钥匙i的时候,我们需要把i钥匙锁在的点u,连接到有了i钥匙的那一层对应的位置上v,权值是0,这样就建成了1>>种类 的图,
    并且图直接有连接。最后跑一遍最短路即可。
    ps:要记录总层数的总点数,之后dis[]的初始化时所有点。

    我觉得好难理解TAT,还是上一种方法好理解

    //下面是最短路得做法,我还没看
    /*
    这里涉及到的变化还蛮多的,我们要先算出最多边数:1>>10*10*10=1024000;
    把原来矩阵每个坐标存成一个点,再对可到邻边赋权值1,在建图的时候,我们只要连接一个双向边即可,假设j是从i左移一格,那么i就是j右移一格。上下同理。
    值得注意的是,我们每次在一层的时候,在没有钥匙i的时候,我们需要把i钥匙锁在的点u,连接到有了i钥匙的那一层对应的位置上v,权值是0,这样就建成了1>>种类 的图,
    u(锁在的位置)--->v(钥匙的位置),权值为0 
    并且图直接有连接。最后跑一遍最短路即可。
    ps:要记录总层数的总点数,之后dis[]的初始化时所有点。
    */ 
     //分层求最短路 
    #include<bits/stdc++.h>
    using namespace std;
    const int N = 12;
    const int M = 1024100;
    const int INF = 0x3f3f3f3f;
    typedef pair<int,int> P;
    struct keyn{
    	int x,y;
    }key[N][20];//key[i][j] 种类为i的钥匙第j把的坐标 
    int n,m,p,s,k,cnt,layer,nn,nsum;//n宽,m长,p种类,s总钥匙数,k总障碍数,cnt计数器,layer层数,nn每层的点,nsum总点数 
    	//layer = 1<<p; //层数为 2^(钥匙种类数) 
    	//nn = n*m;  //每一层点数 
    	//nsum = n*m*layer;   //总共点数 
    int num[N][N],fg[200][200];
    int head[M],nex[M],ver[M],edge[M];
    int hadk[N],kn[N],vis[M],dis[M];
    void add(int x,int y,int w){
    	ver[++cnt] = y;
    	nex[cnt] = head[x];
    	edge[cnt] = w;
    	head[x] = cnt;
    }
    void read(){
    	cnt = 0;
    	int x,y;
    	scanf("%d%d%d%d",&n,&m,&p,&k);
    	for(int i=1;i<=n;i++)
    	for(int j=1;j<=m;j++)
    	num[i][j] = ++cnt; ///把每个左边当成一个点 
    	for(int i=1,g,u,v;i<=k;i++){
    		scanf("%d%d",&x,&y); u = num[x][y];  //把两个坐标连成一条边 
    		scanf("%d%d",&x,&y); v = num[x][y];
    		scanf("%d",&g);
    		if(g==0) g=-1;
    		fg[u][v] = fg[v][u] = g;
    	}
    	scanf("%d",&s);
    	for(int i = 1,q;i <= s;i++){
    		scanf("%d%d%d",&x,&y,&q);
    		kn[q]++; //这种钥匙的数量 
    		key[q][kn[q]].x = x;
    		key[q][kn[q]].y = y;
    	}
    }
    void build(){
    	layer = 1<<p; //层数为 2^(钥匙种类数) 
    	nn = n*m;  //每一层点数 
    	nsum = n*m*layer;   //总共点数 
    	
    	for(int t=0;t<layer;t++){
    		for(int i=1;i<=p;i++){
    			if(t&(1<<(i-1))) hadk[i] = 1;
    			else hadk[i] = 0;
    		}
    		for(int i=1;i<=n;i++)
    			for(int j=1;j<=m;j++){
    				int u = num[i][j],v = num[i][j+1];//向右连边
    				if(v && fg[u][v]!=-1) 
    				if(fg[u][v]==0 || hadk[fg[u][v]]){  //可以走或者是有钥匙 就连边 
    					add(t*nn+u,t*nn+v,1);
    					add(t*nn+v,t*nn+u,1);
    				}
    				
    				v = num[i+1][j]; //向下连边
    				if(v && fg[u][v]!=-1) 
    				if(fg[u][v]==0 || hadk[fg[u][v]]){  //可以走或者是有钥匙 就连边 
    					add(t*nn+u,t*nn+v,1);  //层数*每一层点数 
    					add(t*nn+v,t*nn+u,1);
    				}
    			}
    		for(int i=1;i<=p;i++){
    			if(!hadk[i]) //没有钥匙才可以移动状态 
    			for(int j=1;j<=kn[i];j++){       //如果这一层没有这样的钥匙,那么就把这种所对应的钥匙的地方连线,边权为0 
    				int u = num[key[i][j].x][key[i][j].y];
    				add(t*nn+u,( t|(1<<(i-1)) ) *nn+u,0);
    			}
    		}	
    	}
    }
    void dj(){
    	priority_queue<P> q;
    	for(int i = 0;i <= nsum; i++) dis[i] = INF;
    	q.push(make_pair(0,1)),dis[1] = 0;
    	//first是距离,second是位置 
    	while(q.size()){
    		int u = q.top().second;
    		q.pop();
    		if(vis[u]) continue;
    		vis[u] = 1;
    		for(int i = head[u];i;i = nex[i]){
    			int v = ver[i],w=edge[i];
    			if(dis[v] > dis[u] + w){
    				dis[v] = dis[u] + w;
    				q.push(make_pair(-dis[v],v));
    			}
    		}
    	}
    }
    void spfa(){
    	queue<int> q;
    	for(int i = 0;i <= nsum;i++) dis[i] = INF;
    	q.push(1),dis[1] = 0,vis[1] = 1;
    	while(q.size()){
    		int u = q.front(); q.pop();vis[u] = 0;
    		for(int i=head[u];i;i = nex[i]){
    			int v = ver[i],w=edge[i];
    			if(dis[v] > dis[u] + w){
    				dis[v] = dis[u] + w;
    				if(!vis[v]){
    					q.push(v),vis[v] = 1;
    				}
    			}
    		}
    		
    	}
    }
    void solve(){
    	int ans = INF;
    	for(int i =0;i<layer;i++)
    	ans = min(ans,dis[i*nn+num[n][m]]);
    	if(ans==INF) printf("-1
    ");
    	else printf("%d
    ",ans);
    }
    int main(){
    	read();
    	build();
    	//spfa();
    	dj();
    	solve();
    	return 0;
    } 

    1496:【例 3】架设电话线

    第一种方法:dp+最短路

    d[i][j]:从1号点到i号点已经用了j次变成0边后路径上的最长边.
    若有一跳从u到v长度为w的边,则 d[v][num] = max(d[u][num],w);
    用dis[u][num] 更新dis[v][num+1]的最小值

    #include<iostream>
    #include<cstring>
    #include<cmath>
    #include<algorithm>
    #include<stack>
    #include<cstdio>
    #include<queue>
    #include<map>
    #include<vector>
    #include<set>
    using namespace std;
    const int maxn=4010;
    const int INF=0x3fffffff;
    typedef long long LL;
    //第一种方法:dp+最短路
    /*
    d[i][j]:从1号点到i号点已经用了j次变成0边后路径上的最长边.
    若有一跳从u到v长度为w的边,则 d[v][num] = max(d[u][num],w);
    用dis[u][num] 更新dis[v][num+1]的最小值
    */ 
    struct node{
    	int now,kk,w;
    	//now表示现在的位置,kk表示已经减少了kk条路径的费用,w表示费用 
    	node(){}
    	node(int noww,int kkk,int ww){
    		now=noww;kk=kkk;w=ww;
    	}
    	bool operator < (const node &b)const{
    		return w>b.w;
    	}
    };
    int head[maxn],to[maxn],next[maxn],wei[maxn];
    int n,m,k,vis[maxn][21],d[maxn][21],cnt;
    //用vis[ i ][ j ] 代表到达 i 用了 j 次免费机会的情况是否出现过)
    //注意后面这个数组,d表示代表到达 i 用了 j 次免费机会的最小花费
    void add(int a,int b,int c){
    	to[++cnt]=b;
    	wei[cnt]=c;
    	next[cnt]=head[a];
    	head[a]=cnt;
    } 
    void inti(){
    	scanf("%d %d %d",&n,&m,&k);
    	for(int i=1;i<=m;i++){
    		int x,y,z;
    		scanf("%d %d %d",&x,&y,&z);
    		add(x,y,z);add(y,x,z);
    	}
    }
    void dij(){
    	for(int i=0;i<=n;i++)
    	for(int j=0;j<=k;j++) d[i][j]=INF;  //表示从1到i号时,第j+1的最长边
    	memset(vis,0,sizeof(vis));
    	priority_queue<node> q;
    	q.push(node(1,0,0));
    	d[1][0]=0; //初始化 
    	while(!q.empty()){
    		node topp=q.top();
    		q.pop();
    		int u=topp.now;  //现在的位置 
    		int num=topp.kk;   //已经找到的
    		if(vis[u][num]) continue;
    		vis[u][num]=1;
    		for(int i=head[u];i;i=next[i]){
    			int v=to[i];
    			int w=wei[i];
    			if(d[v][num]>max(d[u][num],w)){  //如果比上一个节点的都大,现在这条边或者前一个节点的dinum大
    			 d[v][num]=max(d[u][num],w);
    			 if(!vis[v][num])
    				q.push(node(v,num,d[v][num]));
    			}
    			if(num<k&&d[v][num+1]>d[u][num]){  //用dis[u][num] 更新dis[v][num+1]的最小值 
    				d[v][num+1]=d[u][num];
    				if(!vis[v][num+1]) 
    				q.push(node(v,num+1,d[v][num+1]));	
    			}
    		} 
    	} 
    }
    int main(){
    	inti();
    	dij();
    	if(d[n][k]!=INF) printf("%d
    ",d[n][k]);
    	else printf("-1
    ");
    return 0;
    }

    第二种:分层求最短路
    我们把节点扩展到二维,二元组(x,p)代表一个节点,从(x,p)到(y,p)上有一个长为w的边,(x,p)到(y,p+1)上有长度为0的边,最后对所有层求最短路,
    分层的时候只能从低层到高层连大家都知道吧,因为你不能说我把一个边变成0后再变回去。

    同一层边权是多少就是多少,不同层就是0

    #include<iostream>
    #include<cstring>
    #include<cmath>
    #include<algorithm>
    #include<stack>
    #include<cstdio>
    #include<queue>
    #include<map>
    #include<vector>
    #include<set>
    using namespace std;
    const int maxn=1010;
    const int maxm=2e5+10;
    const int INF=0x3fffffff;
    typedef long long LL;
    /*
    第二种:分层求最短路
    我们把节点扩展到二维,二元组(x,p)代表一个节点,从(x,p)到(y,p)上有一个长为w的边,(x,p)到(y,p+1)上有长度为0的边,最后对所有层求最短路,
    分层的时候只能从低层到高层连大家都知道吧,因为你不能说我把一个边变成0后再变回去。
    */
    typedef pair<int,int> pp;
    int n,m,k,cnt;
    int head[maxm],to[maxm],wei[maxm],next[maxm];
    int vis[maxm],dis[maxm];
    void add(int x,int y,int z){
    	to[++cnt]=y;
    	wei[cnt]=z;
    	next[cnt]=head[x];
    	head[x]=cnt;
    }
    void inti(){
    	scanf("%d %d %d",&n,&m,&k);
    	for(int i=1;i<=m;i++){
    		int x,y,z;
    		scanf("%d %d %d",&x,&y,&z);
    		add(x,y,z);
    		add(y,x,z);
    		for(int j=1;j<=k;j++){
    			//同一层滴
    			add(j*n+x,j*n+y,z);add(j*n+y,j*n+x,z);
    			//不是同一层的 
    			add((j-1)*n+x,j*n+y,0);add((j-1)*n+y,j*n+x,0); 
    		}
    	}
    }
    void dij1(){
    	priority_queue<pp,vector<pp>,greater<pp> > q;
    	for(int i=0;i<=maxm;i++) dis[i]=INF;
    	dis[1]=0;
    	q.push(make_pair(0,1));
    	while(!q.empty()){
    		int op=q.top().second;  //下标 
    		q.pop();
    		if(vis[op]) continue;
    		vis[op]=1;
    		for(int i=head[op];i;i=next[i]){
    			int v=to[i];
    			int w=wei[i];
    			if(dis[v]>max(dis[op],w)){  //最大值 
    				dis[v]=max(dis[op],w);
    				if(!vis[v]) q.push(make_pair(dis[v],v));
    			}
    		}
    	}
    }
    
    
    int main(){
    	inti();
    	dij1();
    	if(dis[k*n+n]==INF) printf("-1
    ");
    	else printf("%d
    ",dis[k*n+n]);
    	return 0;
    } 

    第三种方法,也是最常用的 二分+最短路
    没错,这是一道二分+最短路验证的题。
    事实上,题目要求的东西已经提示了要使用二分。毕竟是求路径上第k+1大条边的权值的最小值。所以这东西怎么二分判定?
    干脆二分答案吧,二分路径上第k+1大条边的权值的最小值是k,然后怎么判定?
    等等,这样的话不比k大的边都没用了啊,也就是说,只有比k大的边会影响答案,这样的话如果通过不超过题目数量限制的剩下的大边无法到达终点的话,显然k取小了,不然的话可以继续将k缩小。
    因此算法流程就很明显了,对于二分出来的值v,所有边中权值比v大的真实边权为1,其它的边真实边权为0,然后在新图上跑最短路,跑出来如果最短路长度没有超出题目限制,就让v减小,
    否则让v增大。

    #include<iostream>
    
    //第三种方法,也是最常用的  二分+最短路
    /*
    没错,这是一道二分+最短路验证的题。
    事实上,题目要求的东西已经提示了要使用二分。毕竟是求路径上第k+1大条边的权值的最小值。所以这东西怎么二分判定?
    干脆二分答案吧,二分路径上第k+1大条边的权值的最小值是k,然后怎么判定?
    等等,这样的话不比k大的边都没用了啊,也就是说,只有比k大的边会影响答案,这样的话如果通过不超过题目数量限制的剩下的大边无法到达终点的话,显然k取小了,不然的话可以继续将k缩小。
    因此算法流程就很明显了,对于二分出来的值v,所有边中权值比v大的真实边权为1,其它的边真实边权为0,然后在新图上跑最短路,跑出来如果最短路长度没有超出题目限制,就让v减小,
    否则让v增大。
    */ 
    #include<bits/stdc++.h>
    using namespace std;
    const int N = 2e5+10,INF = 0x3f3f3f3f;
    int head[N],ver[N],nex[N],edge[N],vis[N],dis[N];
    typedef pair<int,int> P;
    int l=0,r=1e6,mid;
    int n,m,k,cnt;
    void add(int x,int y,int w){
    	ver[++cnt] = y;
    	edge[cnt] = w;
    	nex[cnt] = head[x];
    	head[x] = cnt;
    }
    void read(){
    	int x,y,w;
    	scanf("%d%d%d",&n,&m,&k);
    	for(int i=1;i<=m;i++){
    		scanf("%d%d%d",&x,&y,&w);
    		add(x,y,w);add(y,x,w);
    	} 
    }
    bool check(int x){
    	for(int i=0;i<N;i++) 
    	dis[i] = INF,vis[i]=0;	
    	priority_queue<P> q;
    	q.push(make_pair(0,1));dis[1] = 0;
    	while(q.size()){
    		int u =q.top().second;q.pop();
    		if(vis[u]) continue;
    		vis[u] = 1;
    		for(int i=head[u];i;i=nex[i]){
    			int v = ver[i],w = edge[i];
    			int nd = dis[u] + (w>x?1:0);
    			//改变实际权值,只有比设定的x大的权值为1,小的为0 
    			if(dis[v] > nd){
    				dis[v] = nd;
    				if(!vis[v])
    				q.push(make_pair(-dis[v],v));  //因为默认是大顶堆。。。 
    			}
    		}
    	}
    	return (dis[n] <= k);   //如果算出来的距离小于k,那么就说明x取大了,比x大的数量少,所以减小x,也就是令right=mid-1 
    }
    void solve(){
    	while(l<=r){
    		mid = ((r+l)>>1);	
    		if(check(mid))
    			r = mid-1;
    		else l = mid+1;	
    	}
    	if(r==1e6) puts("-1");
    	else printf("%d
    ",l);
    }
    int main(){
    	read();
    	solve();
    	return 0;
    }

    1497:农场派对

    建立正反图

    求一个来回最短路,有向图

    的时候是用反边图求,回来的时候是正向边,所以建立正反图,分别spfa,然后取max(mm,dis1[i]+dis2[i])

    #include<bits/stdc++.h>
    #define inf 2000000000
    using namespace std;
    struct edge{
        int to,next,w;
    }e[100001],e2[100001];
    int n,m,s;
    int head[1001],head2[1001];
    int in[1001];
    int d1[1001];  //正图的dis 
    int d2[1001]; //反图的dis 
    
    void spfa(){
        queue<int> q;
        q.push(s);
        in[s]=1;
        d1[s]=0;   //对正图 
        while(!q.empty()){
            int t=q.front();
            q.pop();
            in[t]=0;
            for(int i=head[t];i!=0;i=e[i].next){   //对正图 
                if(d1[t]+e[i].w<d1[e[i].to]){
                    d1[e[i].to]=d1[t]+e[i].w;
                    if(!in[e[i].to]){
                        in[e[i].to]=1;
                        q.push(e[i].to);
                    }
                }
            }
        }
    }
    void spfa2(){
        queue<int> q;
        q.push(s);
        in[s]=1;
        d2[s]=0;   //对反图 
        while(!q.empty()){
            int t=q.front();
            q.pop();
            in[t]=0;
            for(int i=head2[t];i!=0;i=e2[i].next){  //对反图 
                if(d2[t]+e2[i].w<d2[e2[i].to]){
                    d2[e2[i].to]=d2[t]+e2[i].w;
                    if(!in[e2[i].to]){
                        in[e2[i].to]=1;
                        q.push(e2[i].to);
                    }
                }
            }
        }
    }
    int main(){
        cin>>n>>m>>s;
        for(int i=1;i<=n;i++){
            d1[i]=inf;
            d2[i]=inf;
        }
        for(int i=1;i<=m;i++){
            int a,b,l;
            scanf("%d %d %d",&a,&b,&l);
            e[i].to=b;
            e[i].next=head[a];
            head[a]=i;   //!!!! 这不就是循环自动增加嘛 
            e[i].w=l; 
            e2[i].to=a;
            e2[i].next=head2[b];
            e2[i].w=l;
            head2[b]=i;  //!!! 
        }
        spfa();
        memset(in,0,sizeof(in));
        spfa2();
        int mx=0;
        for(int i=1;i<=n;i++){
            mx=max(mx,d1[i]+d2[i]);
        }
        cout<<mx;
        return 0;
    }  

    1498:Roadblocks

     无向图,第二短路。

    建立正反图

    可能和第k短路不同吧,正着求一遍最短路,倒着求一遍最短路。最后枚举每条边,如果它两边的dis加上自己的边权小于最短路长度,更新ans。

    #include<iostream>
    #include<cstring>
    #include<cmath>
    #include<algorithm>
    #include<stack>
    #include<cstdio>
    #include<queue>
    #include<map>
    #include<vector>
    #include<set>
    using namespace std;
    const int maxn=5010;
    const int M=1e5+10;
    const int INF=0x3fffffff;
    typedef long long LL;
    //第二最短路,可能和第k短路不同吧
    //正着求一遍最短路,倒着求一遍最短路。
    //最后枚举每条边,如果它两边的dis加上自己的边权小于最短路长度,更新ans。
    int n,m;
    struct node{
    	int to,dis,next;
    }ed[M];
    int head[M],dis[maxn][2];  //0是正着的,1是反着的 
    int vis[maxn],cnt;
    void addnode(int u,int v,int c){
    	ed[++cnt].dis=c;
    	ed[cnt].to=v;
    	ed[cnt].next=head[u];
    	head[u]=cnt;
    }
    void spfa(int s,int op){
    	for(int i=1;i<=n;i++){
    		dis[i][op]=INF;vis[i]=0;
    	}
    	dis[s][op]=0;
    	vis[s]=1;
    	queue<int> q;
    	q.push(s);
    	while(!q.empty()){
    		int ll=q.front();
    		q.pop();
    		vis[ll]=0;
    		for(int i=head[ll];i;i=ed[i].next){
    			int t=ed[i].to;
    			int diss=ed[i].dis;
    			if(dis[t][op]>dis[ll][op]+diss){
    				dis[t][op]=dis[ll][op]+diss;
    				if(!vis[t]){
    					vis[t]=1;
    					q.push(t);
    				}
    			}
    		}
    	}
    }
    int main(){
    	scanf("%d %d",&n,&m);
    	int x,y,z;
    	for(int i=1;i<=m;i++){
    		scanf("%d %d %d",&x,&y,&z);
    		addnode(x,y,z);
    		addnode(y,x,z);
    	}
    	spfa(1,0);   //从起点开始求 ,0代表正图的 
    	spfa(n,1);   //从终点开始求,1代表反图的 
    	int ans=INF;
    	int temp;
    	for(int i=1;i<=n;i++){  //枚举每条边,自己的权值+两个端点的距离 
    		for(int j=head[i];j;j=ed[j].next){
    			temp=dis[i][0]+ed[j].dis+dis[ed[j].to][1];
    			//!!!首先是最最小,然后是还要满足大于最短路 
    			if(temp<ans&&temp>dis[n][0]) ans=temp;
    		}
    	}
    	printf("%d
    ",ans);
    return 0;
    }

    1499:最短路计数

    给出一个 N 个顶点 M 条边的无向无权图,顶点编号为 1∼N。问从顶点 1 开始,到其他每个点的最短路有几条。

    这道题可以直接BFS,求深度,就像和小生成数数量一样,如果if(dep[v]==dep[x]+1),那么就num[v]=(num[v]+num[x])%100003;

    但是这里不是相乘,这是最短路。

    或者spfa也可以

    #include<iostream>
    #include<cstring>
    #include<cmath>
    #include<algorithm>
    #include<stack>
    #include<cstdio>
    #include<queue>
    #include<map>
    #include<vector>
    #include<set>
    using namespace std;
    const int maxn=1010;
    const int INF=0x3fffffff;
    typedef long long LL;
    //....看了题解。。。我好笨,这可是遍历啊。。。
    //直接BFS就可以了。。。或者SPFA 
    
    vector<int> a[100001];
    int num[100001]={0};
    int dep[100001];  //dis改为dep 
    int vis[100001]={0};
    int n,m;
    
    int main(){
    	scanf("%d %d",&n,&m);
    	int x,y;
    	for(int i=1;i<=m;i++){
    		scanf("%d %d",&x,&y);
    		a[x].push_back(y);
    		a[y].push_back(x);
    	}
    	queue<int> q;
    	dep[1]=0;  //起点深度 
    	vis[1]=1;
    	q.push(1);
    	num[1]=1; //初始化完毕
    	while(!q.empty()){
    		int x=q.front();
    		q.pop();
    		for(int i=0;i<a[x].size();i++){
    			int v=a[x][i];
    			if(!vis[v]){
    				vis[v]=1;
    				dep[v]=dep[x]+1;
    				q.push(v);
    			}
    			if(dep[v]==dep[x]+1){   //跟最小生成数数量有异曲同工之妙 
    				num[v]=(num[v]+num[x])%100003;   //!!相加呀 ,这里不是相乘了,因为这是最短路, 
    			}
    		}
    	} 
    	for(int i=1;i<=n;i++){
    			printf("%d
    ",num[i]);
    	} 
    return 0;
    }

    1500:新年好

     最短路,但是需要经过五个地点,任意顺序

    我想的比较复杂了,其实就是暴力+spfa,因为一共就5个地点,你把每个地点到其他地方的距离都算出来, 其实这样的话暴力也没多久的

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <queue>
    #define inf 0x7fffffff/2
    using namespace std;
    struct node {
    	int to,next,wei;
    }e[200005];
    int h[50005],cnt,a[10];
    int dt[50]; //当前的访问顺序 
    int d[7][50005];  //每个地点到其他地方的最短距离 
    int vis[50005];
    int n,m,ans=inf;
    void adg(int x,int y,int z) {
    	e[++cnt]=(node){y,h[x],z};
    	h[x]=cnt;
    }
    void spfa(int v0,int k) {
    	queue<int> q;
    	int book[50005]={0};
    	q.push(v0);
    	book[v0]=1;
    	d[k][v0]=0;  //每个地点到其他地方的最短距离 
    	while (!q.empty()) {
    		int w=q.front();
    		q.pop();
    		book[w]=0;
    		for (int i = h[w];i;i=e[i].next) {
    			int y=e[i].to;
    			if (d[k][y]>d[k][w]+e[i].wei) {
    				d[k][y]=d[k][w]+e[i].wei;
    				if (!book[y]) {
    					book[y]=1;
    					q.push(y);
    				}
    			}
    		}
    	}
    }
    void dfs(int k) {
    	if (k==6) {
    		int s=0;
    		for (int i = 1;i < 6;i++) {
    			s+=d[dt[i]][a[dt[i+1]]]; //当前这个顺序下,这个点到下一个点的距离相加 
    		}
    		ans=min(ans,s);
    		return;
    	}
    	for (int i = 2;i <= 6;i++) {
    		if (!vis[i]) {
    			vis[i]=1;
    			dt[k+1]=i;
    			dfs(k+1);
    			vis[i]=0;
    		}
    	}
    }
    int main() {
    	scanf("%d%d",&n,&m);
    	for (int i = 2;i <= 6;i++) {
    		scanf("%d",a+i);
    	}
    	for (int i = 1;i <= m;i++) {
    		int a,b,c;
    		scanf("%d%d%d",&a,&b,&c);
    		adg(a,b,c);
    		adg(b,a,c);
    	}
    	memset(d,127,sizeof(d));
    	a[1]=1;
    	dt[1]=1;
    	for (int i = 1;i <= 6;i++) {
    		spfa(a[i],i);
    	}
    	dfs(1);
    	printf("%d",ans);
    	return 0;
    }
     

    1501:最优贸易

    从1到n,有单向道路也有双向道路
    在某个点买入,在某个点卖出,求最大差价。没有距离了,建立正反图的另一个运用!

    建立正图E1,反图E2
      SPFA1求解从1出发到达每个点的路径上价格最小值Min[],到达不了则Min[]=∞
      SPFA2求解从N出发到达每个点的路径上价格最大值Max[],到达不了则Max[]=0
      枚举每个点计算差值Max[i]-Min[i],最大差值即为答案(Min[i]!=∞&&Max[i]!=0)
    证明
    因为我们正向的跑了一边spfa更新最小值就相当于把起点到每个点的最小值求出来了
    然后我第二遍spfa就做了两件事情第一个证明了连通性【因为我这个题目中有一些路是单向的,所以说可能某些点买的价格特别低或者特别高,但是我去的那儿是陷阱,
    你进去了就出不来了这样你的答案就是错的了,所以我把图反过来建这样你从终点可以反着跑到这个点也就代表这个点可以正着跑到终点,这就相当于证明了连通性】
    以此保证最小买入点在最大卖出点前

    #include<iostream>
    #include<cstring>
    #include<cmath>
    #include<algorithm>
    #include<stack>
    #include<cstdio>
    #include<queue>
    #include<map>
    #include<vector>
    #include<set>
    using namespace std;
    const int maxn=100010;
    const int M=500010;
    const int INF=0x3fffffff;
    typedef long long LL;
    //从1到n,有单向道路也有双向道路
    //在某个点买入,在某个点卖出,求最大差价
    /*
      建立正图E1,反图E2
      SPFA1求解从1出发到达每个点的路径上价格最小值Min[],到达不了则Min[]=∞
      SPFA2求解从N出发到达每个点的路径上价格最大值Max[],到达不了则Max[]=0
      枚举每个点计算差值Max[i]-Min[i],最大差值即为答案(Min[i]!=∞&&Max[i]!=0)
    证明
    因为我们正向的跑了一边spfa更新最小值就相当于把起点到每个点的最小值求出来了
    然后我第二遍spfa就做了两件事情第一个证明了连通性【因为我这个题目中有一些路是单向的,所以说可能某些点买的价格特别低或者特别高,但是我去的那儿是陷阱,
    你进去了就出不来了这样你的答案就是错的了,所以我把图反过来建这样你从终点可以反着跑到这个点也就代表这个点可以正着跑到终点,这就相当于证明了连通性】
    以此保证最小买入点在最大卖出点前
    */ 
    struct node{
    	int to,next;
    }ed1[M],ed2[M];
    int head1[M],head2[M];
    int maxx[maxn],minn[maxn];
    int vis1[maxn],vis2[maxn];
    int wei[maxn];
    int cnt1,cnt2,n,m;
    void add(int u,int v){
    	ed1[++cnt1].to=v;
    	ed1[cnt1].next=head1[u];
    	head1[u]=cnt1;
    	ed2[++cnt2].to=u;
    	ed2[cnt2].next=head2[v];
    	head2[v]=cnt2;
    } 
    void spfa1(){  // 寻找路径上最小价格点(从源点出发)
    	memset(vis1,0,sizeof(vis1));
    	for(int i=1;i<=n;i++) minn[i]=INF;
    	queue<int> q1;
    	q1.push(1);
    	minn[1]=wei[1];
    	vis1[1]=1;
    	while(!q1.empty()){
    		int t=q1.front();
    		q1.pop();
    		vis1[t]=0;
    		for(int i=head1[t];i;i=ed1[i].next){
    			int v=ed1[i].to;
    			int tt=min(minn[t],wei[v]); //求最小值,所以直接比较就可以了 
    			if(minn[v]>tt){
    				minn[v]=tt;
    				if(!vis1[v]){
    					vis1[v]=1;
    					q1.push(v);
    				}
    			}
    		}
    	}
    }
    void spfa2(){ //寻找从N点出发所能到达的最大价格处
    	memset(vis2,0,sizeof(vis2));
    	vis2[n]=1;
    	queue<int> q2;
    	q2.push(n);
    	while(!q2.empty()){
    		int t=q2.front();
    		q2.pop();
    		vis2[t]=0;
    		for(int i=head2[t];i;i=ed2[i].next){
    			int v=ed2[i].to;
    			int tt=max(maxx[t],wei[v]);
    			if(maxx[v]<tt){
    				maxx[v]=tt;
    				if(!vis2[v]){
    					vis2[v]=1;
    					q2.push(v);
    				}
    			}
    		}
    		
    	}
    }
    int main(){
    	scanf("%d %d",&n,&m);
    	for(int i=1;i<=n;i++){
    		scanf("%d",&wei[i]);
    	}
    	int x,y,f;
    	for(int i=1;i<=m;i++){
    		scanf("%d %d %d",&x,&y,&f);
    		if(f==1){
    			add(x,y);
    		}
    		else if(f==2){
    			add(x,y);
    			add(y,x);
    		}
    	}
    	spfa1();  // 寻找路径上最小价格点(从源点出发)
    	spfa2();  //寻找从N点出发所能到达的最大价格处
    	int ans=-1;
    	for(int i=1;i<=n;i++){ //计算最大差值 
    		if(minn[i]!=INF&&maxx[i]!=0)  //!!!连通性保障:都能到达 
    		ans=max(ans,maxx[i]-minn[i]); //保证此点在从1到N的路径上
    	}
    	printf("%d",ans);
    return 0;
    }

    1502:汽车加油行驶问题

    这道题也可以用分层图解决 +spfa(也就这个好些一点
    也可以用普通的bfs+spfa解决(我更喜欢这个wwww
    即总共建k+1层图,只有层与层之间有边,汽车每走一步就会向上移动一层。建边规则满足题目要求即可。

    因为k是能走的长度,但是我还是不太能理解建边的过程

    #include<bits/stdc++.h>
    #define N 200005
    using namespace std;
    int Map[105][105];
    int num[105][105][15];
    
    struct ss
    {
        int v,next,w;
    };
    ss edg[N*4];
    int head[N],now_edge=0;
    
    void addedge(int u,int v,int w)
    {
        edg[now_edge]=(ss){v,head[u],w};
        head[u]=now_edge++;
    }
    int dis[N];
    int vis[N]={0};
    
    void spfa()
    {
        for(int i=0;i<N;i++)dis[i]=INT_MAX/2;
        dis[num[1][1][0]]=0;  //节点(离散后) 
        queue<int>q;
        q.push(num[1][1][0]);
        
        vis[num[1][1][0]]=1;
        
        while(!q.empty())
        {
            int now=q.front();
            q.pop();
            vis[now]=0;
            
            for(int i=head[now];i!=-1;i=edg[i].next)
            {
                int v=edg[i].v;
                if(dis[v]>dis[now]+edg[i].w)
                {
                    dis[v]=dis[now]+edg[i].w;
                    
                    if(!vis[v])
                    {
                        q.push(v);
                        vis[v]=1;
                    }
                }
            }
        }
    }
    
    
    int main()
    {
        int n,k,a,b,c;
        memset(head,-1,sizeof(head));
        scanf("%d %d %d %d %d",&n,&k,&a,&b,&c);
        for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)scanf("%d",&Map[i][j]);  //1为有油库,0为没有 
        
        int cnt=1;
        for(int kk=0;kk<=k;kk++)
        {
            for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
            num[i][j][kk]=cnt++; //转化为节点 
        }
        
        //0--1层 
        for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
        {
            if(i+1<=n)addedge(num[i][j][0],num[i+1][j][1],0);
            if(j+1<=n)addedge(num[i][j][0],num[i][j+1][1],0);
            if(i-1>=1)addedge(num[i][j][0],num[i-1][j][1],b);
            if(j-1>=1)addedge(num[i][j][0],num[i][j-1][1],b);
        }
        //(1~k-1)--(2~k)层 
        for(int kk=1;kk<k;kk++)
        for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
        if(!Map[i][j])  //没有油库 
         {
            if(i+1<=n)addedge(num[i][j][kk],num[i+1][j][kk+1],0);
            if(j+1<=n)addedge(num[i][j][kk],num[i][j+1][kk+1],0);
            if(i-1>=1)addedge(num[i][j][kk],num[i-1][j][kk+1],b);
            if(j-1>=1)addedge(num[i][j][kk],num[i][j-1][kk+1],b);
            addedge(num[i][j][kk],num[i][j][0],c+a);  //没有油库,还需要加上c 
        }
        else   //有油库的话,就只需要费用a并且是从0走到kk层 
        {
            addedge(num[i][j][kk],num[i][j][0],a);
        }
        
        for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
        if(!Map[i][j])
        {
            addedge(num[i][j][k],num[i][j][0],c+a);   //没有油库,还需要加上c 
        }
        else
        {
            addedge(num[i][j][k],num[i][j][0],a);//有油库的话,就只需要费用a并且是从0走到kk层 
        }
        
        spfa();
        int ans=INT_MAX;  
        for(int i=0;i<=k;i++)ans=min(ans,dis[num[n][n][i]]);  //求最小 
        printf("%d
    ",ans); 
        return 0;
    } 

    发现了另一种做法:

    在最短路上面除了x,y坐标以外再加一维,表示剩下的油

    其实就是最短路啊SPFA

    如果值为1强制加油,如果没油了强制建加油站,如果x或y坐标增量为负强制加b,然后这样操作以后的价钱少于下一个点的x,y,you的价钱,就可以push

    如果已经到(n,n)了但是没油了不用建加油站

    #include<iostream>
    #include<cstring>
    #include<cmath>
    #include<algorithm>
    #include<stack>
    #include<cstdio>
    #include<queue>
    #include<map>
    #include<vector>
    #include<set>
    using namespace std;
    const int maxn=1010;
    const int INF=0x3fffffff;
    typedef long long LL;
    typedef unsigned long long ull;
    int n,k,a,b,c;
    int mon[110][110][11];
    int vis[110][110][11];
    int dis[4][2]={{0,1},{0,-1},{1,0},{-1,0}};
    struct node{
    	int x,y,you;
    };
    queue<node> q;
    int mp[110][110];
    void spfa(){
    	memset(mon,0x3f,sizeof(mon));
    	node st;
    	st.x=1;st.y=1;st.you=k;
    	mon[1][1][k]=0;
    	vis[1][1][k]=1;
    	q.push(st);
    	while(!q.empty()){
    		int x,y,v;
    		x=q.front().x;y=q.front().y;v=q.front().you;
    		q.pop();
    		vis[x][y][v]=0;
    		for(int i=0;i<4;i++){
    			if(!(x+dis[i][0]&&y+dis[i][1]&&(x+dis[i][0]<=n)&&(y+dis[i][1]<=n))) continue;
    			int e=mon[x][y][v],w=v-1;
    			if(dis[i][0]<0||dis[i][1]<0) e+=b;
    			if(mp[x+dis[i][0]][y+dis[i][1]]) e+=a,w=k;
    			if(!w&&(x+dis[i][0]!=n||y+dis[i][1]!=n)) w=k,e+=a+c;
    			if(mon[x+dis[i][0]][y+dis[i][1]][w]>e){
    				node nex;
    				mon[x+dis[i][0]][y+dis[i][1]][w]=e;
    				nex.x=x+dis[i][0];
    				nex.y=y+dis[i][1];
    				nex.you=w;
    				if(!vis[nex.x][nex.y][nex.you]) q.push(nex);
    				vis[nex.x][nex.y][nex.you]=1;
    			}
    		} 
    		/* 
    		node op=q.front();
    		q.pop();
    		vis[op.x][op.y][op.you]=0;
    		//cout<<op.you<<endl;
    		for(int i=0;i<4;i++){
    			int xx=op.x+dis[i][0];
    			int yy=op.x+dis[i][1];
    			if(!(xx&&yy&&xx<=n&&yy<=n)) continue;
    			int e=mon[op.x][op.y][op.you];
    			int w=op.you-1; //要耗油! 
    			//如果值为1强制加油,如果没油了强制建加油站,如果x或y坐标增量为负强制加b
    			node nex;
    			nex.x=xx;nex.y=yy; 
    			if(mp[xx][yy]) {
    				e+=a;w=k;
    			}
    			if(dis[i][0]<0||dis[i][1]<0) e+=b;
    			if(!w&&(xx!=n&&yy!=n)){
    				e=e+a+c;
    				w=k;
    			}
    			nex.you=w;
    			if(mon[xx][yy][w]>e){
    				mon[xx][yy][w]=e;
    				if(!vis[xx][yy][w]){
    					q.push(nex);
    				}
    				vis[xx][yy][w]=1;
    			} 
    		}
    		*/ 
    	}
    }
    int main(){
    	scanf("%d %d %d %d %d",&n,&k,&a,&b,&c);
    	for(int i=1;i<=n;i++)
    	for(int j=1;j<=n;j++) scanf("%d",&mp[i][j]);
    	int ans=INF;
    	spfa();
    	//cout<<endl;
    	for(int i=0;i<=k;i++){
    		//cout<<mon[n][n][i]<<endl;
    		ans=min(ans,mon[n][n][i]);
    	}
    	printf("%d
    ",ans);
    return 0;
    }
    

    1503:道路和航线

    看了题解,我一开始想的是用SPFA,但是卡了,所以可以用SLF优化,(第一种方法)

    对于SPFA算法而言,它的优化有两种,我们今天使用SLF优化算法.
    众所周知,SPFA算法是一种鉴于队列的实现算法.每一次有节点加入队列都是加入队尾.
    但是SLF优化,不同于一般的SPFA算法,它是一种利用双端队列算法处理的问题.
    如果说当前点所花费的值少于我们当前队头点的值的话,那么我们就将这个节点插入到队头去,否则我们还是插入到队尾.
    这个就是非常好用的SLF优化算法.

    //我的代码卡了一个点 艹
    #include<iostream>
    #include<cstring>
    #include<cmath>
    #include<algorithm>
    #include<stack>
    #include<cstdio>
    #include<queue>
    #include<map>
    #include<vector>
    #include<set>
    using namespace std;
    const int M = 4e5 + 10 ;
    const int INF = 0x3f3f3f3f ;
    int n , m ;
    int e[M] , h[M] , ne[M] , w[M] , idx , dis[M] ;
    //to,head,next,wei,cnt 
    bool st[M] ;
    void add(int a , int b , int c)
    {
    	e[idx] = b , ne[idx] = h[a] , w[idx] = c , h[a] = idx ++ ;
    }
    void spfa(int s)
    {
    	deque<int> q ;//!!!! 
    	memset(dis ,0x3f , sizeof dis) ;
    	q.push_back(s) ;
    	dis[s] = 0 ;
    	st[s] = true ;
    	while(q.size())
    	{
    		int u = q.front() ;
    		q.pop_front() ;
    		st[u] = false ;
    		for(int i = h[u] ;i != -1 ;i = ne[i])
    		 {
    		 	int j = e[i] ;
    		 	if(dis[j] > dis[u] + w[i])
    		 	 {
    		 	 	dis[j] = dis[u] + w[i] ;
    		 	 	if(!st[j])
    		 	 	 {
    		 	 	 	st[j] = 1 ;
    		 	 	 	if(q.size() && dis[j] < dis[q.front()])
    		 	 	 	 q.push_front(j) ;
    		 	 	 	else q.push_back(j) ;
    				   }
    			  }
    		 }
    	}
    	return ;
    }
    int main()
    {
    	int T , R , P , S ;
    	memset(h , -1 , sizeof h) ;
    	scanf("%d%d%d%d" , &T , &R , &P , &S) ;
    	while(R --)
    	  {
    	  	int a , b , c ;
    	  	scanf("%d%d%d" , &a , &b , &c) ;
    	  	add(a , b , c) , add(b , a , c) ;
    	  }
    	while(P --)
    	 {
    	 	int a , b , c ;
    	 	scanf("%d%d%d" , &a , &b , &c) ;
    	 	add(a , b , c) ;
    	 } 
    	spfa(S) ;
    	for(int i = 1 ;i <= T ;i ++)
    	 {
    	 	if(dis[i] == INF) printf("NO PATH
    ");
    	 	else printf("%d
    " , dis[i]) ;
    	 }
    	return 0 ;
    } 
     

    第二种方法,把双边缩点成DAG,然后添加单向边,求最短路
    题意说明了没有负环。双向边总是正的。
    所以可以缩点,得到一个DAG。
    每个联通块内做dijstra,然后各个联通块之间拓扑更新最短路就行了。

    #include<bits/stdc++.h>
    using namespace std;
    #define N 500100
    template<typename T>inline void read(T &x) {
        x=0;T f=1,ch=getchar();
        while(!isdigit(ch))  {if(ch=='-')  f=-1;  ch=getchar();}
        while(isdigit(ch))  {x=x*10+ch-'0';  ch=getchar();}
        x*=f;
    }
    typedef pair <int,int> pii;
    priority_queue <pii,vector<pii>,greater<pii> > q1;
    queue<int> q;
    int T, P, R, S, tot, x, y, z;
    int dis[N], vis[N], lin[N];  //后面这个就相当于head 
    int c[N], deg[N];  //col和入度 
    vector <int> cir[N];   //每个联通块里面的节点 
    struct gg{
        int y,next,v;
    }a[N<<1];
    
    inline void add(int x, int y, int z) {
        a[++tot].y = y;
        a[tot].v = z;
        a[tot].next = lin[x];
        lin[x] = tot;
    }
    
    void dfs(int now,int na){  //找出这个连通块的所有节点 
        c[now] = na;  //标记col 
        cir[na].push_back(now);
        for (int i = lin[now]; i; i = a[i].next){
            int y = a[i].y;
            if (!c[y]) dfs(y,na);
        }
    }
    
    int main() {
        //freopen("1.in","r",stdin);
        read(T); read(R); read(P); read(S);
        for (int i = 1; i <= R; i++) {
            read(x); read(y); read(z);
            add(x, y, z);
            add(y, x, z);
        }
        int num = 0;
        for (int i = 1; i <= T; i++) {
            if(!c[i]) dfs(i, ++num); //在添加有向边之前,先找到连通块,形成DAG 
        }
        for (int i = 1; i <= P; i++) {
            read(x); read(y); read(z); 
            add(x, y, z);
            ++deg[c[y]];  //求拓扑排序嘛 
        }
        q.push(c[S]);
        for (int i = 1; i <= num; i++) {
            if (!deg[i]) q.push(i);   //入队为0的先进去 
        }
        memset(dis, 0x7f, sizeof(dis));
        dis[S] = 0;
        while (!q.empty()) {  //q里面放入度为0 的点 
            int now = q.front(); q.pop();
            for (int i = 0; i < cir[now].size(); i++) {  //把这个联通块的节点都弄进去 
                q1.push(make_pair(dis[cir[now][i]],cir[now][i]));   //q1存储的是Pair,first是距离,second是编号,这个是小顶堆 
            }
            while(!q1.empty()) {  //然后都弹出来,然后就看有向边的 
                int x = q1.top().second;
    			q1.pop();
                if (vis[x]) continue;
                vis[x] = 1;
                for (int i = lin[x]; i; i = a[i].next) {
                    int y = a[i].y;
                    if (dis[y] > dis[x] + a[i].v) {
                        dis[y] = dis[x] + a[i].v;
                        if (c[x] == c[y]) q1.push(make_pair(dis[y], y));  //如果是在同一个联通块,就还是放进去 
                    }
                    if (c[x] != c[y]) {  //不在同一个,就入度减1 
                        deg[c[y]]--;
                        if (!deg[c[y]]) q.push(c[y]);
                     }
                }
            }    
        }
        for (int i = 1; i <= T; i++) {
            if (dis[i] > 0x3f3f3f3f) printf("NO PATH
    ");
            else printf("%d
    ", dis[i]);
        }
        return 0;
    } 
    

      

  • 相关阅读:
    cocos2d-x 坐标系
    Linux 用户和用户组
    Linux 挂载分区 + swap 分区
    Linux 分区 磁盘分区与格式化
    Linux MBR分区(重点知识)
    Linux -磁盘管理 ip http://blog.csdn.net/xh16319/article/details/17272455
    Linux 底行模式常用命令
    Linux Bash 通配符
    Linux Bash 的基本功能 管道符
    Linux Bash的基本功能 输出重定向
  • 原文地址:https://www.cnblogs.com/shirlybaby/p/12547580.html
Copyright © 2011-2022 走看看