7.27 图论 存图 前向星 最短路 dijstra算法 SPFA算法
存图
1.vector(操作简单,浪费空间)
2.前向星:最高效的存图方式
链式结构,结构体保存边
const int maxn=1e5+10;//注意这里的maxn应该开题目给的双倍,因为边是双向的,加边还要开更多 const int inf=1e9; int n,m; int head[maxn]; //head[i]表示以i为起点的最后一条边的编号; struct edge { int to;//这条边的终点 int w; //权重(边长) int last; //与自己起点相同的上一条边的编号 }Edge[maxn];//边数组 int cnt; //记录当前边的编号 void add(int u,int v,int w)//加边 //起点u,终点e,权重w; { Edge[cnt].to=v; Edge[cnt].w=w; Edge[cnt].last=head[u];//现在是要把编号为cnt的边加进来, head[u]=cnt++;//现在cnt边加进来了,cnt边为以u为起点的最后一条边 }
图的遍历
for(int j=head[i];j!=-1;j=Edge[j].last) { int v=Edge[j].to; }
最短路
Dijstra算法:适用于求边权为正,由单个源点出发到其他所有点的最短路
//Dijkstra int dis[maxn]; struct node { int u;//编号 int d;//距离 bool operator<(const node &res)const{return d>res.d;};//从小到大排序 node(int uu,int dd):u(uu),d(dd){} }; void dijstra(int s)//求图中其它点到源点s的最短路,求出结果存放在dis数组中 { priority_queue<node> q; //优先队列 while(!q.empty())q.pop(); for(int i=1;i<=n;i++) { dis[i]=inf;//初始化所有距离都无限大 } dis[s]=0;//s到自己本身的距离为0 q.push(node(s,0)); while(!q.empty()) { node nx=q.top(); q.pop(); int u=nx.u; for(int i=head[u];i!=-1;i=Edge[i].last) { int to=Edge[i].to; int w=Edge[i].w; if(dis[u]+w<dis[to]) { dis[to]=dis[u]+w; q.push(node(to,dis[to])); } } } return; }
SPFA算法:可以处理负边,不能有负的双向边,一定会存在负环,那样子就没有最小的了,因为可以到无穷小(可以有正的双向边)
//SPFA long long dis[maxn];//dis[i]为源点到i的最短路径长度 int in[maxn]; //in[i]表示i点入队次数 bool vis[maxn];// vis[i]表示i点是否在队列中 int spfa(int s)//用spfa求解源点s到其他点的最短路径,结果放在dis中,存在负环,返回1,不存在返回-1 { queue<int> q; memset(vis,false,sizeof vis); memset(in,0,sizeof in); for(int i=1;i<=n;i++) { dis[i]=inf; } dis[s]=0; q.push(s); vis[s]=true; in[s]=1;//顶点入队vis要做标记,另外要统计顶点的入队次数 while(!q.empty()) { int u=q.front(); q.pop(); vis[u]=false;//u(队头元素)出队,不在队列中 for(int i=head[u];i!=-1;i=Edge[i].last) { int to=Edge[i].to; int w=Edge[i].w; if(dis[u]+w<dis[to]) { dis[to]=dis[u]+w;//松弛 if(!vis[to])//如果to点不在队列中 { vis[to]=true;//让to在队列中 in[to]++;//to的入队次数+1 q.push(to);//让to入队 if(in[to]>=n)return 1;//如果这个点的入队次数超过次数上限(所有点的个数),则存在负环,返回1 } } } } return -1;//不存在负环 }
A题:
averyboy现在在实习。每天早上他要步行去公司上班,你肯定知道,他是一个非常男孩,所以他会选择最短的路去公司。现在给你averyboy到公司途中的若干个站点,标号为1~N,averyboy的开始在1号站点,它的公司在N号站点,然后给你若干条边代表站点有路可以通过(可能会有重边)。现在你需要告诉averyboy他到公司的最短路径是多少。
模板题:
#include<bits/stdc++.h> using namespace std; const int maxn=1e5+10;//注意这里的maxn应该开题目给的双倍,因为边是双向的,加边还要开更多 const int inf=1e9; int n,m; int head[maxn]; //head[i]表示以i为起点的最后一条边的编号; struct edge { int to;//这条边的终点 int w; //权重(边长) int last; //与自己起点相同的上一条边的编号 }Edge[maxn];//边数组 int cnt; //记录当前已有的边数 void add(int u,int v,int w)//加边 //起点u,终点e,权重w; { //cout<<1<<endl; Edge[cnt].to=v; Edge[cnt].w=w; Edge[cnt].last=head[u];//现在是要把编号为cnt的边加进来, head[u]=cnt++;//现在cnt边加进来了,cnt边为以u为起点的最后一条边 } //Dijkstra int dis[maxn]; struct node { int u;//编号 int d;//距离 bool operator<(const node &res)const{return d>res.d;};//从小到大排序 node(int uu,int dd):u(uu),d(dd){} }; void dijstra(int s)//求图中其它点到源点s的最短路,求出结果存放在dis数组中 { priority_queue<node> q; while(!q.empty())q.pop(); for(int i=1;i<=n;i++) { dis[i]=inf;//初始化所有距离都无限大 } dis[s]=0;//s到自己本身的距离为0 q.push(node(s,0)); while(!q.empty()) { node nx=q.top(); q.pop(); int u=nx.u; for(int i=head[u];i!=-1;i=Edge[i].last) { int to=Edge[i].to; int w=Edge[i].w; if(dis[u]+w<dis[to]) { dis[to]=dis[u]+w; q.push(node(to,dis[to])); //cout<<1<<endl; } } } return; } int main() { int t; cin>>t; while(t--) { int i; scanf("%d%d",&n,&m); for(i=1;i<=n;i++)head[i]=-1; cnt=1; for(i=1;i<=m;i++) { int u,v,w; scanf("%d%d%d",&u,&v,&w); add(u,v,w); add(v,u,w); } dijstra(1); int ans=dis[n]; if(ans==inf)printf("averyboynb\n"); else printf("%d\n",ans); } return 0; }
B题:
Input
接下来T组测试数据。
每组测试数据第一行为两个整数N,M,k1, k2(1 <= N <= 1000, 0 <= M <= 10000)代表站点的个数和边的条数以及起点的个数,终点的个数(1 <= k1, k2 <= N)
接下来一行k1个数x[i],代表averyboy起点(1 <= x[i] <= N)
接下来一行k2个数y[i],代表终点(1 <= y[i] <= N)
接下来M行,每一行三个数u, v, w代表站点u,v之间有一条无向边(可能会有重边),边的权值为w(1 <= u, v <= N, 0 <= w <= 1000)
解题思路:
建立一个超级源点s,与起点集合的所有点连边,权值为0,建立一个汇点t,与所有终点集合的点连边,权值为0,对s做dijstra,求dis[t]即可。
#include <bits/stdc++.h> using namespace std; const int maxn=1e5+10; const int inf=(int)0x3f3f3f3f; int n,m,k1,k2; int x[1100]; int y[1100]; int head[1100]; //head[i]表示以i为起点的最后一条边的编号; struct edge { int to;//这条边的终点 int w; //权重(边长) int last; //与自己起点相同的上一条边的编号 }Edge[maxn];//边数组 int cnt; //记录当前已有的边数 void add(int u,int v,int w)//加边 //起点u,终点v,权重w; { //cout<<1<<endl; Edge[cnt].to=v; Edge[cnt].w=w; Edge[cnt].last=head[u];//现在是要把编号为cnt的边加进来, head[u]=cnt++;//现在cnt边加进来了,cnt边为以u为起点的最后一条边 } //Dijkstra long long dis[maxn]; struct node { int u;//编号 int d;//距离 bool operator<(const node &res)const{return d>res.d;};//从小到大排序 node(int uu,int dd):u(uu),d(dd){} }; void dijstra(int s)//求图中其它点到源点s的最短路,求出结果存放在dis数组中 { priority_queue<node> q; while(!q.empty())q.pop(); for(int i=0;i<=n+1;i++) { dis[i]=inf;//初始化所有距离都无限大 } dis[s]=0;//s到自己本身的距离为0 q.push(node(s,0)); while(!q.empty()) { node nx=q.top(); q.pop(); int u=nx.u; for(int i=head[u];i!=-1;i=Edge[i].last) { //cout<<1<<endl; int to=Edge[i].to; int w=Edge[i].w; if(dis[u]+w<dis[to]) { dis[to]=dis[u]+w; q.push(node(to,dis[to])); //cout<<1<<endl; } } } return; } int main() { int t; cin>>t; while(t--) { scanf("%d%d%d%d",&n,&m,&k1,&k2); cnt=1; int i; for(i=0;i<=n+1;i++)head[i]=-1; int s=0,t=n+1; for(i=1;i<=k1;i++) { scanf("%d",&x[i]); add(s,x[i],0); add(x[i],s,0); } for(i=1;i<=k2;i++) { scanf("%d",&y[i]); add(y[i],t,0); add(t,y[i],0); } for(i=1;i<=m;i++) { int u,v,w; scanf("%d%d%d",&u,&v,&w); add(u,v,w); add(v,u,w); } dijstra(s); int ans=dis[t]; if(ans!=inf)printf("%d\n",ans); else printf("averyboynb\n"); } return 0; }
C题:
Input
接下来T组测试数据
每组测试数据第一行为一个正整数N(N <= 1e5)代表城市的个数
接下来一行N个整数a[i],代表书在每个城市的价格(1 <= a[i] <= 10000)
接下来N - 1行,每行三个数u, v, w(1 <= u, v <= N, 1 <= w <= 1000)代表城市u,v之间有一条权值为w的边
Output
Sample Input
1
4
10 40 15 30
1 2 30
1 3 2
3 4 10
Sample Output
8
HINT
他选择从1号城市买书,到4号城市卖书,然后他买书和路费一共花费10 + 2 + 10 = 22,到了4号城市把书卖掉,赚30元,所以最终赚了30 - 22 = 8元,这种情况下他能赚的最多。
解题思路:边权分离 源点汇点
建立一个超级源点s,与所有点连边,边的权值即等于连线点的书的价值,建立一个汇点t,与所有点连边,边的权值等于负的连线点的书的价值
因为有负的边权,所以用SPFA,对s做SPFA求dis[t]即可
求出来的t是最小的(买书钱+路费-卖书钱),它的负数即是最大的(卖书钱-路费-买书钱)
#include <bits/stdc++.h> using namespace std; const int maxn=1e6+10; const int inf=1e9; int n; int head[maxn]; //head[i]表示以i为起点的最后一条边的编号; //head[i]表示以i为起点的最后一条边的编号; struct edge { int to;//这条边的终点 int w; //权重(边长) int last; //与自己起点相同的上一条边的编号 }Edge[maxn];//边数组 int cnt; //记录当前已有的边数 void add(int u,int v,int w)//加边 //起点u,终点e,权重w; { Edge[cnt].to=v; Edge[cnt].w=w; Edge[cnt].last=head[u];//现在是要把编号为cnt的边加进来, head[u]=cnt++;//现在cnt边加进来了,cnt边为以u为起点的最后一条边 } //SPFA long long dis[maxn];//dis[i]为源点到i的最短路径长度 int in[maxn]; //in[i]表示i点入队次数 bool vis[maxn];// vis[i]表示i点是否在队列中 int spfa(int s)//用spfa求解源点s到其他点的最短路径,结果放在dis中,存在负环,返回1,不存在返回-1 { queue<int> q; memset(vis,false,sizeof vis); memset(in,0,sizeof in); for(int i=0;i<=n+1;i++) { dis[i]=inf; } dis[s]=0; q.push(s); vis[s]=true; in[s]=1;//顶点入队vis要做标记,另外要统计顶点的入队次数 while(!q.empty()) { int u=q.front(); q.pop(); vis[u]=false;//u(队头元素)出队,不在队列中 for(int i=head[u];i!=-1;i=Edge[i].last) { int to=Edge[i].to; int w=Edge[i].w; if(dis[u]+w<dis[to]) { dis[to]=dis[u]+w;//松弛 if(!vis[to])//如果to点不在队列中 { vis[to]=true;//让to在队列中 in[to]++;//to的入队次数+1 q.push(to);//让to入队 if(in[to]>=n)return 1;//如果这个点的入队次数超过次数上限(所有点的个数),则存在负环,返回1 } } } } return -1;//不存在负环 } int book[maxn]; int main() { int t; cin>>t; while(t--) { scanf("%d",&n); int i; for(i=0;i<=n+1;i++)head[i]=-1; cnt=1; int s=0,t=n+1; for(i=1;i<=n;i++) { scanf("%d",&book[i]); add(s,i,book[i]); add(i,t,-book[i]); } for(i=1;i<=n-1;i++) { int u,v,w; scanf("%d%d%d",&u,&v,&w); add(u,v,w); add(v,u,w); } int b=spfa(s); long long ans=-dis[t]; printf("%lld\n",ans); } return 0; }
D题:
Description
Input
接下来T组测试数据
每组测试数据的第一行三个整数N, A, B(1 <= N <= 100, 1 <= A, B <= N)分别代表火车站的数量以及averyboy的起点站和终点站
接下来N行数据,第i行第一个数为k,代表第i个火车站有k个出口,后面k个整数(k个整数可能会有若干个相同),代表每个出口通向的下一个火车站编号,k个数中的第一个表示这个火车站默认的出口。(0 <= k <= N)
Output
Sample Input
2
3 2 1
2 2 3
2 3 1
2 1 2
3 1 2
2 3 2
1 3
1 1
Sample Output
0
1
HINT
理解题意:即是说若与火车站相连的出口是默认出口,则不会受领导批评,即边权为0,若不是默认出口,会受到批评,边权为1,求最短路即可。(权值可以有距离,价值等多重意义)
#include <bits/stdc++.h> using namespace std; const int maxn=1e5+10; const int inf=1e9; int n; int head[maxn]; //head[i]表示以i为起点的最后一条边的编号; struct edge { int to;//这条边的终点 int w; //权重(边长) int last; //与自己起点相同的上一条边的编号 }Edge[maxn];//边数组 int cnt; //记录当前已有的边数 void add(int u,int v,int w)//加边 //起点u,终点v,权重w; { Edge[cnt].to=v; Edge[cnt].w=w; Edge[cnt].last=head[u];//现在是要把编号为cnt的边加进来, head[u]=cnt++;//现在cnt边加进来了,cnt边为以u为起点的最后一条边 } //Dijkstra int dis[maxn]; struct node { int u;//编号 int d;//距离 bool operator<(const node &res)const{return d>res.d;};//从小到大排序 node(int uu,int dd):u(uu),d(dd){} }; void dijstra(int s)//求图中其它点到源点s的最短路,求出结果存放在dis数组中 { priority_queue<node> q; while(!q.empty())q.pop(); for(int i=1;i<=n;i++) { dis[i]=inf;//初始化所有距离都无限大 } dis[s]=0;//s到自己本身的距离为0 q.push(node(s,0)); while(!q.empty()) { node nx=q.top(); q.pop(); int u=nx.u; for(int i=head[u];i!=-1;i=Edge[i].last) { int to=Edge[i].to; int w=Edge[i].w; if(dis[u]+w<dis[to]) { dis[to]=dis[u]+w; q.push(node(to,dis[to])); //cout<<1<<endl; } } } return; } int main() { int t; cin>>t; while(t--) { int a,b; scanf("%d%d%d",&n,&a,&b); int i,j; for(i=1;i<=n;i++) { head[i]=-1; } cnt=1; for(i=1;i<=n;i++) { int k; scanf("%d",&k); for(j=1;j<=k;j++) { int v; scanf("%d",&v); if(j==1)add(i,v,0); else add(i,v,1); } } dijstra(a); int ans=dis[b]; if(ans==inf)printf("averyboynb\n"); else printf("%d\n",ans); } return 0; }