程序设计作业题 week7
to sum up
本周主要学习三个算法,分别是floyd(处理多源最短路径,算法简单,但复杂度为n^3,需要注意三层for循环的顺序问题),dijkstra(处理图中不存在负边单源最短路径,复杂度O((n+m)logn)),SPFA(处理存在负边的单源最短路径,可以用来判断负环)。
一、A题 TT的魔法猫(Floyd)
N个人进行比赛,每两个人有一个胜负关系,该胜负关系可传递(A胜B,B胜C,则可判断A胜C)。先给定M个胜负关系,求还有多少对选手的胜负关系无法确定。
1.Sample input and output
input
第一行给出数据组数。
每组数据第一行给出 N 和 M(N , M <= 500)。
接下来 M 行,每行给出 A B,表示 A 可以胜过 B。
3
3 3
1 2
1 3
2 3
3 2
1 2
2 3
4 2
1 2
3 4
output
对于每一组数据,判断有多少场比赛的胜负不能预先得知。注意 (a, b) 与 (b, a) 等价,即每一个二元组只被计算一次。
0
0
4
2.解题思路及代码
主要练习使用floyd算法,求多源(最短)路径。个人的解题思路主要是,通过松弛操作记录推测出的胜负关系个数,用原总关系个数-初始化关系数-推测出的胜负关系数即等于无法确定的关系数。除此之外还需要进行适当的剪枝操作,即如果folyd第二层dis[I][k]==0时,即第一个胜负关系无法确定,第三层的for循环可以跳过。
#include<iostream>
#include<cstdio>
using namespace std;
const int N=550;
const int M=550;
int dis[N][N]={0};
int n,m,t;
int cnt;
void init()
{
cnt=0;
memset(dis,0,sizeof(dis));
}
void Floyd()
{
for(int k=1;k<=n;k++)
for(int i=1;i<=n;i++)
{
if(dis[i][k]==0)
continue;
for(int j=1;j<=n;j++) {
if ((dis[i][k] & dis[k][j]) > dis[i][j]) {
dis[i][j] = 1;
cnt++;
}
}
}
}
int main()
{
scanf("%d",&t);
while(t--)
{
init();
scanf("%d",&n);
scanf("%d",&m);
for(int i=0;i<m;i++)
{
int a,b;
scanf("%d",&a);
scanf("%d",&b);
dis[a][b]=1;
}
Floyd();
//cout<<cnt<<endl;
cout<<(n*(n-1)/2)-cnt-m<<endl;
}
return 0;
}
总结:本道题主要注意folyd的三次循环的嵌套顺序和可行性剪枝。
二、B题 TT的旅行日记
N个站点(包含一个起点和一个终点),其中有M条经济线,K条商业线。每条线路都有起点终点和时间消耗值,要求从起点到终点,输出乘坐任意条经济线和最多一条商业线的最短时间消耗的路径。
1.Sample input and output
input
输入包含多组数据。每组数据第一行为 3 个整数 N, S 和 E (2 ≤ N ≤ 500, 1 ≤ S, E ≤ 100),即猫猫快线中的车站总数,起点和终点(即喵星机场所在站)编号。
下一行包含一个整数 M (1 ≤ M ≤ 1000),即经济线的路段条数。
接下来有 M 行,每行 3 个整数 X, Y, Z (1 ≤ X, Y ≤ N, 1 ≤ Z ≤ 100),表示 TT 可以乘坐经济线在车站 X 和车站 Y 之间往返,其中单程需要 Z 分钟。
下一行为商业线的路段条数 K (1 ≤ K ≤ 1000)。
接下来 K 行是商业线路段的描述,格式同经济线。
所有路段都是双向的,但有可能必须使用商业车票才能到达机场。保证最优解唯一。
4 1 4
4
1 2 2
1 3 3
2 4 4
3 4 5
1
2 4 3
output
对于每组数据,输出3行。第一行按访问顺序给出 TT 经过的各个车站(包括起点和终点),第二行是 TT 换乘商业线的车站编号(如果没有使用商业线车票,输出"Ticket Not Used",不含引号),第三行是 TT 前往喵星机场花费的总时间。
本题不忽略多余的空格和制表符,且每一组答案间要输出一个换行
1 2 4
2
5
2.解题思路及代码
整体思路是使用两边dij算法,在经济线中分别求出从起点和终点的单源最短路径。然后遍历每一条商业线,松弛操作起点到商业线起点+商业线+商业线终点到终点。
首先对图的存储使用前向星的方式,Edge[0][N]和Edge[1][N]分别存储从起点的和从终点的。使用两次dij,分别把最短距离存在dis[2][N]里。
在输出路径是时,主要利用了prehead数组,prehead[0][N]主要记录了从起点开始dij时,每个节点的前向节点,prehead[1][N]主要记录了从终点开始的前向(从整体路径上看是后向节点),然后用一个队列来不断访问从起点到商务线起点的节点并存储,用栈来访问从商务线终点到路径终点,然后输出队列,输出商务线,输出栈,即可得到路径输出。
#include<iostream>
#include<cstdio>
#include<queue>
#include<stack>
#include<algorithm>
#include<cstring>
#define inf 1e8;
const int M=1e6;
const int N=1e3+100;
using namespace std;
int Station_num,Road_num,SRoad_num;
//存储从起点开始遍历和从终点开始遍历的前向点,用于输出路径
int prehead[2][N];
struct edge
{
public:
int u;
int v;
int w;
int nxt;
};
//第一层存储经济线,第二层存储商务线
edge Edges[2][M];
int head[2][N],tot[2];
priority_queue<pair<int,pair<int,int>>> q;
//l区分添加边的层数={0,1}
void addEdge(int u,int v,int w,int l)
{
Edges[l][tot[l]].u=u;
Edges[l][tot[l]].v=v;
Edges[l][tot[l]].w=w;
Edges[l][tot[l]].nxt=head[l][u];
head[l][u]=tot[l];
tot[l]++;
}
//dis两层分别存储从起点std和从终点end的dij结果
int vis[N],dis[2][N];
//dij 数组
void dijkstra(int s,int l)
{
while(q.size()) q.pop();
for(int i=1;i<=Station_num;i++)
{
vis[i]=0;
dis[l][i]=inf;
}
dis[l][s]=0;
q.push(make_pair(0,make_pair(s,-1)));
while(q.size())
{
//找到堆顶最小元素
int x=q.top().second.first;
int num=q.top().second.second;
q.pop();
if(vis[x]) continue;
prehead[l][x]=num;
vis[x]=1;
//松弛操作
for(int i=head[0][x];i!=-1;i=Edges[0][i].nxt)
{
int y=Edges[0][i].v,w=Edges[0][i].w;
if(dis[l][y]>dis[l][x]+w)
{
dis[l][y]=dis[l][x]+w;
q.push(make_pair(-dis[l][y],make_pair(y,Edges[0][i].u)));
}
}
}
}
//用于存储商务线的两端点到起点、终点的路径
stack<int> path1;
queue<int> path2;
void init()
{
tot[0]=0;
tot[1]=0;
memset(head,-1,sizeof(head));
memset(prehead,-1,sizeof(prehead));
while(!path1.empty())
path1.pop();
while(!path2.empty())
path2.pop();
}
int main()
{
int yat=0;
while(cin>>Station_num)
{
init();
int st,end;
scanf("%d",&st);
scanf("%d",&end);
scanf("%d",&Road_num);
for(int i=0;i<Road_num;i++)
{
int x,y,z;
scanf("%d",&x);
scanf("%d",&y);
scanf("%d",&z);
addEdge(x,y,z,0);
addEdge(y,x,z,0);
}
dijkstra(st,0);
//从st开始到达所有点的距离存储在dis[0]中
dijkstra(end,1);
//从end开始到达所有点的距离存储在dis[1]中
scanf("%d",&SRoad_num);
for(int i=0;i<SRoad_num;i++)
{
int x,y,z;
scanf("%d",&x);
scanf("%d",&y);
scanf("%d",&z);
addEdge(x,y,z,1);
addEdge(y,x,z,1);
}
int ans=dis[0][end];
int p1=-1,p2=-1;
for(int i=0;i<tot[1];i++)
{
int x=Edges[1][i].u,y=Edges[1][i].v;
int tmp=dis[0][x]+dis[1][y]+Edges[1][i].w;
if(tmp<ans)
{
ans=tmp;
p1=x;
p2=y;
}
}
if(yat!=0)
cout<<endl;
if(p1==-1&&p2==-1) // 不用经过商务线
{
int lim=end;
while(lim!=-1)
{
path1.push(lim);
lim=prehead[0][lim];
}
while(1)
{
if(path1.empty())
break;
cout<<path1.top();
path1.pop();
if(!path1.empty())
cout<<' ';
}
cout<<endl;
cout<<"Ticket Not Used"<<endl;
}
else{
int lim=p1;
while(lim!=-1)
{
path1.push(lim);
lim=prehead[0][lim];
}
while(!path1.empty())
{
cout<<path1.top()<<' ';
path1.pop();
}
lim=p2;
while(lim!=end)
{
path2.push(lim);
lim=prehead[1][lim];
}
path2.push(end);
while(path2.front()!=end)
{
cout<<path2.front()<<' ';
path2.pop();
}
cout<<end<<endl;
cout<<p1<<endl;
}
cout<<ans<<endl;
yat=1;
}
return 0;
}
三、C题 TT的美梦 (SPFA)
现有N个城市,每个城市有一个繁荣度。现有M条有向道路,每条道路的税费等于 (目的地繁荣程度 - 出发地繁荣程度)^ 3
先需要多次查询从1号城市到任意城市所需税费情况。
1.Sample input and output
input
第一行输入 T,表明共有 T 组数据。(1 <= T <= 50)
对于每一组数据,第一行输入 N,表示点的个数。(1 <= N <= 200)
第二行输入 N 个整数,表示 1 ~ N 点的权值 a[i]。(0 <= a[i] <= 20)
第三行输入 M,表示有向道路的条数。(0 <= M <= 100000)
接下来 M 行,每行有两个整数 A B,表示存在一条 A 到 B 的有向道路。
接下来给出一个整数 Q,表示询问个数。(0 <= Q <= 100000)
每一次询问给出一个 P,表示求 1 号点到 P 号点的最少税费。
2
5
6 7 8 9 10
6
1 2
2 3
3 4
1 5
5 4
4 5
2
4
5
10
1 2 4 4 5 6 7 8 9 10
10
1 2
2 3
3 1
1 4
4 5
5 6
6 7
7 8
8 9
9 10
2
3 10
output
每个询问输出一行,如果不可达或税费小于 3 则输出 ‘?’。
Case 1:
3
4
Case 2:
?
?
2.解题思路及代码
主要利用SPFA进行负环的判断。如果某个点被松弛次数超过n-1次,则该点必定出现在负环上,然后对该点使用dfs,将该点能到达的所有点全部打上负环标记,在SPFA操作的入队列操作中,如果入队元素被打上了负环标记则可以直接continue。
#include<iostream>
#include<queue>
#include<cstring>
const int inf=2e8;
const int N=1e6;
const int M =205;
using namespace std;
int head[M],tot;
int a[M];
int n,m,Q;
int tar[M];
int re(int n1,int n2)
{
int ans=(n2-n1);
return (ans*ans*ans);
}
struct edge
{
public:
int u;
int v;
int w;
int nxt;
}Edges[N];
void addEdge(int u,int v,int w)
{
Edges[tot].u=u;
Edges[tot].v=v;
Edges[tot].w=w;
Edges[tot].nxt=head[u];
head[u]=tot;
tot++;
}
int Vis[M]; //用于DFS
void init()
{
tot=0;
memset(head,-1,sizeof(head));
memset(a,0,sizeof(a));
}
queue<int> q;
int vis[M],dis[M],cnt[M]; //vis用于标记元素是否在队列中
//重制DFS访问标记
void dfs(int u)
{
tar[u]=1; //打上负环标记
//Vis[u]=1; //打上访问标记
for(int i=head[u];i!=-1;i=Edges[i].nxt)
{
if(tar[Edges[i].v]==0)
dfs(Edges[i].v);
}
}
void SPFA(int s)
{
while(!q.empty()) q.pop();
for(int i=1;i<=M;i++)
{
vis[i]=0;
tar[i]=0;
cnt[i]=0;
dis[i]=inf;
}
dis[s]=0;
vis[s]=1;
q.push(s);
while(!q.empty())
{
int x=q.front(); q.pop();
vis[x]=0;
//如果在负环
if(tar[x]==1)
continue;
for(int i=head[x];i!=-1;i=Edges[i].nxt)
{
int y=Edges[i].v;
//如果在负环
if(tar[y]==1)
continue;
//松弛操作
if(dis[y]>dis[x]+Edges[i].w)
{
dis[y]=dis[x]+Edges[i].w;
cnt[y]=cnt[x]+1;
if(cnt[y]>=n)
{
//memset(Vis,0, sizeof(Vis));
dfs(y);
//continue;
}
else if(vis[y]!=1 && tar[y]!=1)
vis[y]=1,q.push(y);
}
}
}
}
int main()
{
int T;
cin>>T;
for(int k=0;k<T;k++)
{
init();
cin>>n;
for(int i=1;i<=n;i++)
cin>>a[i];
cin>>m;
for(int i=0;i<m;i++)
{
int n1,n2;
cin>>n1;
cin>>n2;
addEdge(n1,n2,re(a[n1],a[n2]));
}
SPFA(1);
cout<<"Case "<<k+1<<':'<<endl;
cin>>Q;
for(int i=0;i<Q;i++)
{
int P;
cin>>P;
if(dis[P]<3 || tar[P]==1|| dis[P]==inf)
cout<<'?'<<endl;
else
cout<<dis[P]<<endl;
}
}
return 0;
}