A-
众所周知,TT 有一只魔法猫。 这一天,TT 正在专心致志地玩《猫和老鼠》游戏,然而比赛还没开始,聪明的魔法猫便告诉了 TT 比赛的最终结果。TT 非常诧异,不仅诧异于他的小猫咪居然会说话,更诧异于这可爱的小不点为何有如此魔力? 魔法猫告诉 TT,它其实拥有一张游戏胜负表,上面有 N 个人以及 M 个胜负关系,每个胜负关系为 A B,表示 A 能胜过 B,且胜负关系具有传递性。即 A 胜过 B,B 胜过 C,则 A 也能胜过 C。 TT 不相信他的小猫咪什么比赛都能预测,因此他想知道有多少对选手的胜负无法预先得知,你能帮帮他吗?
Input
第一行给出数据组数。 每组数据第一行给出 N 和 M(N , M <= 500)。 接下来 M 行,每行给出 A B,表示 A 可以胜过 B。
Output
对于每一组数据,判断有多少场比赛的胜负不能预先得知。注意 (a, b) 与 (b, a) 等价,即每一个二元组只被计算一次。
Sample Input
3 3 3 1 2 1 3 2 3 3 2 1 2 2 3 4 2 1 2 3 4
Sample Output
0 0 4
思路分析:
本题考察Floyd-Warshall算法,这个算法是解决图中每两点的距离问题的,我们只要记得如下三重循环即可。
for(int k=0;k<n;k++){ for(int i=0;i<n;i++){ for(int j=0;j<n;j++){ d[i][j]=min(d[i][j],d[i][k]+d[k][j]); } } }
我们可以得到这样一个关系:如果a>b,b>c,则就有a>c,该关系具有传递性,所以这个题的目的就是让我们求一个传递闭包。所以我们可以将边权换成1来处理,只要表达出传递性即可。因为要考虑图中任意两点的距离问题,所以可以用弗洛伊德算法来求解。
对于本题我们只要求传递闭包,所以只要将代码改成
d[i][j]=d[i][j]||(d[i][k]&&d[k][j])
即可。
用处:最短路、求传递闭包。复杂度O(n^3),有时要优化。
对于本题,可以进行适当的剪枝操作。我们可以在第三重循环前加一个判断,如果i,k比赛不可知,那么第三重循环中的判断肯定为0,所以无需进行第三重循环,直接跳过即可。只有当i,k比赛可知时,第三重循环才有意义。
以下是源代码:
#include<iostream> using namespace std; int n,m; bool d[510][510]; int cnt; int main(){ int c; scanf("%d",&c); for(int ii=0;ii<c;ii++){ scanf("%d %d",&n,&m); for(int i=1;i<=n;i++){ for(int j=1;j<=n;j++){ d[i][j]=false; if(i==j){ d[i][j]=true; } } } //不能把它理解为无向图 ,因为 如果 a>b a>c, a c不能判断 ,要把它理解为有向图 int a,b; for(int i=0;i<m;i++){ scanf("%d %d",&a,&b); d[a][b]=true; } // for(int i=1;i<=n;i++){ // for(int j=i+1;j<=n;j++){ // for(int k=i+1;k<j;k++){ // d[i][j]=d[i][j]||(d[i][k]&&d[k][j]); // } // } // } for(int k=1;k<=n;k++){ for(int i=1;i<=n;i++){ if(d[i][k]==false){ continue; } for(int j=1;j<=n;j++){ d[i][j]=d[i][j]||(d[i][k]&&d[k][j]); } } } cnt=0; for(int i=1;i<=n;i++){ for(int j=i+1;j<=n;j++){ if(d[i][j]==false&&d[j][i]==false){ cnt++; } } } cout<<cnt<<endl; } }
B-
众所周知,TT 有一只魔法猫。
今天他在 B 站上开启了一次旅行直播,记录他与魔法猫在喵星旅游时的奇遇。 TT 从家里出发,准备乘坐猫猫快线前往喵星机场。猫猫快线分为经济线和商业线两种,它们的速度与价钱都不同。当然啦,商业线要比经济线贵,TT 平常只能坐经济线,但是今天 TT 的魔法猫变出了一张商业线车票,可以坐一站商业线。假设 TT 换乘的时间忽略不计,请你帮 TT 找到一条去喵星机场最快的线路,不然就要误机了!
输入
输入包含多组数据。每组数据第一行为 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 行是商业线路段的描述,格式同经济线。
所有路段都是双向的,但有可能必须使用商业车票才能到达机场。保证最优解唯一。
输出
对于每组数据,输出3行。第一行按访问顺序给出 TT 经过的各个车站(包括起点和终点),第二行是 TT 换乘商业线的车站编号(如果没有使用商业线车票,输出"Ticket Not Used",不含引号),第三行是 TT 前往喵星机场花费的总时间。
本题不忽略多余的空格和制表符,且每一组答案间要输出一个换行
输入样例
4 1 4
4
1 2 2
1 3 3
2 4 4
3 4 5
1
2 4 3
输出样例
1 2 4
2
5
思路分析:
本题我使用的Dijskra算法来写的。Dijksra算法是一个求单源最短路的算法,使用于有向图和无向图,但是它只使用于边权为正的图,对于有负边权的图我们可以使用其他方法来解决,如Bellman-Ford算法(或其队列优化的SPFA算法)。对于Dijksra算法来说,一般来说它的复杂度是O(n^2),但是我们可以使用优先级队列来优化它,可以使它的总体复杂度是O(mlogn),n为顶点数,m为图的边数,当图不是那么稠密的时候这种优化还是有效的。
Dijkstra适用于:解决单源、非负权重;最短路的松弛 : dis[y] > dis[x] +w ,则松弛成功,更新 dis[y] 大小 ,将y加入小根堆中; 算法一定会结束,因为每个点只能够被小根堆弹出一次 。 每个点被弹出之后 ,dis[u] 即为最短路。
对于本题,我们一开始不考虑使用商业票的情况,而是分别从起点和终点开始Dijskra算法,得到两个数组d1,d2。这时我们便可以得到不用商业票的耗费ans,若不连通则耗费无线大。然后我们在考虑使用商业票的情况,对于每一个商业票a,b,c(从a到b耗费c),此时的总耗费为d1【a】+c+d2【b】,然后于ans求小,同时记录下取小的商业票起点和终点,最后便能得到答案。
以下是源代码:
#include<iostream> #include<vector> #include<queue> #include<cstring> #define maxn 1100 #define INF 1000000 using namespace std; struct Edge{ int from,to,dist; Edge(int u, int v, int d):from(u),to(v),dist(d){} }; struct HeapNode{ int d,u; bool operator < (const HeapNode & rhs) const{return d > rhs.d;} }; struct Dijkstra{ int n,m; vector<Edge> edges; vector<int> G[maxn]; bool done[maxn]; //.... void init(int nn){ n=nn; for(int i=0;i<n;i++){ G[i].clear(); } edges.clear(); } void addEdge(int from, int to, int dist){ edges.push_back(Edge(from,to,dist)); m=edges.size(); G[from].push_back(m-1); } void dijkstra(int s,int d[],int p[]){ priority_queue<HeapNode> Q; for(int i=1;i<=n;i++){ d[i]=INF; p[i]=-1;} d[s]=0; p[s]=-1; memset(done,false,sizeof(done)); Q.push((HeapNode){0,s}); while(!Q.empty()){ HeapNode x=Q.top(); Q.pop(); int u =x.u; if(done[u]) continue; done[u]=true; for(int i=0;i<G[u].size();i++){ Edge& e=edges[G[u][i]]; // cout<<"from="<<u<<"="<<e.from<<"to"<<e.to<<"and dist="<<e.dist<<endl; if(d[e.to]> d[u]+ e.dist){ d[e.to]= d[u]+ e.dist; // cout<<"change d["<<e.to<<"]="<<d[u]<<"+"<<e.dist<<endl; p[e.to]= u; Q.push((HeapNode){d[e.to],e.to}); } } } // cout<<"dij="; // for(int i=1;i<=n;i++){ // cout<<d[i]<<" "; // } // cout<<"pre4="; // int temp=4; // while(p[temp]!=-1){ // cout<<temp<<" "; // temp=p[temp]; // } // cout<<temp<<endl; } }; int p1[maxn]; int p2[maxn]; int d1[maxn]={INF}; int d2[maxn]={INF}; vector<Edge> edges; int main(){ int n,s,e; int M,K; Dijkstra D1; bool f=false; while(~scanf("%d%d%d",&n,&s,&e)){ if(f==false){ f=true; }else{ printf(" "); } scanf("%d",&M); int a,b,c; D1.init(n); for(int i=0;i<M;i++){ scanf("%d%d%d",&a,&b,&c); D1.addEdge(a,b,c); D1.addEdge(b,a,c); } D1.dijkstra(s,d1,p1); D1.dijkstra(e,d2,p2); edges.clear(); scanf("%d",&K); for(int i=0;i<K;i++){ scanf("%d%d%d",&a,&b,&c); edges.push_back(Edge(a,b,c)); edges.push_back(Edge(b,a,c)); } int ans=d1[e]; int buss=-1; int bussto=-1; for(int i=0;i<edges.size();i++){ // cout<<"d1"<<edges[i].from<<" "<<d1[edges[i].from]<<"d2"<<edges[i].to<<" "<<d2[edges[i].to]<<"DIST"<<edges[i].dist<<endl; // ans=min(ans,D1.d[edges[i].from]+D2.d[edges[i].to]+edges[i].dist); if(ans>d1[edges[i].from]+d2[edges[i].to]+edges[i].dist){ ans=d1[edges[i].from]+d2[edges[i].to]+edges[i].dist; buss=edges[i].from; bussto=edges[i].to; } } if(buss==-1){ int cnt=1; d1[0]=e; while(p1[e]!=-1){ d1[cnt]=p1[e]; e=p1[e]; cnt++; } printf("%d",d1[cnt-1]); for(int i=1;i<cnt;i++){ printf(" %d",d1[cnt-i-1]); } printf(" Ticket Not Used "); }else{ int cnt=1; d1[0]=buss; int temp=buss; while(p1[temp]!=-1){ d1[cnt]=p1[temp]; temp=p1[temp]; cnt++; } printf("%d",d1[cnt-1]); for(int i=1;i<cnt;i++){ printf(" %d",d1[cnt-i-1]); } // cout<<buss; cnt=1; d2[0]=bussto; temp=bussto; while(p2[temp]!=-1){ d2[cnt]=p2[temp]; temp=p2[temp]; cnt++; } for(int i=0;i<cnt;i++){ printf(" %d",d2[i]); } printf(" %d ",buss); } printf("%d ",ans); } }
C-
这一晚,TT 做了个美梦! 在梦中,TT 的愿望成真了,他成为了喵星的统领!喵星上有 N 个商业城市,编号 1 ~ N,其中 1 号城市是 TT 所在的城市,即首都。 喵星上共有 M 条有向道路供商业城市相互往来。但是随着喵星商业的日渐繁荣,有些道路变得非常拥挤。正在 TT 为之苦恼之时,他的魔法小猫咪提出了一个解决方案!TT 欣然接受并针对该方案颁布了一项新的政策。 具体政策如下:对每一个商业城市标记一个正整数,表示其繁荣程度,当每一只喵沿道路从一个商业城市走到另一个商业城市时,TT 都会收取它们(目的地繁荣程度 - 出发地繁荣程度)^ 3 的税。 TT 打算测试一下这项政策是否合理,因此他想知道从首都出发,走到其他城市至少要交多少的税,如果总金额小于 3 或者无法到达请悄咪咪地打出 '?'。
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 号点的最少税费。
Output
每个询问输出一行,如果不可达或税费小于 3 则输出 '?'。
Sample Input
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
Sample Output
Case 1: 3 4 Case 2: ? ?
思路分析:
本题背景为可能有负边权的单源最短路问题,故dijkstra算法不再适用。我们使用Bellman−Ford算法或队列优化的SPFA算法。本题使用SPFA算法。
首先完成图的输入,根据题意,先记录每个城市的权重WEI[] ,然后再完成图的边的加入,这里要注意的是加入边的时候边权是要根据题意计算得来的,易得边权可能为负边权。SPFA基本原理于Dijskra算法大同小异。先将起点到所有点的距离初始化为inf并将起点入队,当队列不为空时,每次取队首元素,将其出队并将vis值修改为0,进行松弛操作,其邻接点如果不在队列中将它加入队列。这里需要格外注意的是如果出现了负环路的问题。因为出现负环路的话直接就会导致最短路不存在(因为一直重复这个环路可能导致计算得出的最短路无穷小,故这种情况最短路不存在),那么我们如何判断是否出现了环路呢?我们可以使用一个计数数组cnt,当每次完成一次松弛操作的时候就加一,每次入队时判断这个点的cnt值是否超过了n-1,因为在一个不含负环的图中,每个点最多只能完成n-1次松弛操作,如果出现了上述情况,就说明了出现了负环,我们就不能再将它入队了。
以下为源代码:
#include<iostream> #include<vector> #include<queue> #include<cstring> #include<cmath> #define maxn 300 #define INF 1000000000 using namespace std; struct Edge{ int from,to,dist; Edge(int u, int v, int d):from(u),to(v),dist(d){} }; bool j[maxn]; struct SPFA{ int n,m; vector<Edge> edges; vector<int> G[maxn]; bool done[maxn]; bool vis[maxn]; int cnt[maxn]; void init(int nn){ n=nn; for(int i=1;i<=n;i++){ G[i].clear(); } edges.clear(); } void addEdge(int from, int to, int dist){ edges.push_back(Edge(from,to,dist)); m=edges.size(); G[from].push_back(m-1); } void spfa(int s,long long d[],int p[]){ queue<int> Q; for(int i=1;i<=n;i++){ d[i]=INF; p[i]=-1; done[i]=false; cnt[i]=0; j[i]=true; } d[s]=0; done[s]=true; Q.push(s); while(!Q.empty()){ int u = Q.front(); Q.pop(); done[u]=false; if(j[u]==false){ continue; } for(int i=0;i<G[u].size();i++){ Edge& e = edges[G[u][i]]; if(d[e.to]>d[u]+e.dist){ d[e.to] = d[u]+e.dist; p[e.to]=u; if(!done[e.to]&&j[e.to]){ Q.push(e.to); done[e.to]=true; cnt[e.to]=cnt[u]+1; if(cnt[e.to] > n){ // d[e.to]=-INF;//发现有负圈 Bfs(e.to,j); } } } } } } void Bfs(int u,bool j[]){ for(int i=1;i<=n;i++){ vis[i]=false; } j[u]=false; queue<int> Q; Q.push(u); vis[u]=true; while(!Q.empty()){ u=Q.front();Q.pop(); for(int i=0;i<G[u].size();i++){ int temp=edges[G[u][i]].to; j[temp]=false; if(vis[temp]==false){ vis[temp]=true; Q.push(temp); } } } } }; int p1[maxn]; long long d1[maxn]; int WEI[maxn]; int main(){ SPFA S; int T,N,M,Q; cin>>T; for(int ii=0;ii<T;ii++){ cin>>N; S.init(N); for(int i=1;i<=N;i++){ cin>>WEI[i]; } cin>>M; int a,b,wei; for(int i=1;i<=M;i++){ cin>>a>>b; wei=pow(WEI[b]-WEI[a],3); S.addEdge(a,b,wei); // S.addEdge(b,a,-wei); } S.spfa(1,d1,p1); // for(int i=1;i<=N;i++){ // cout<<"D[i] = "<<d1[i]<<" j"<<j[i]<<" ||"; // } // cout<<endl; cin>>Q; cout<<"Case "<<ii+1<<":"<<endl; for(int i=1;i<=Q;i++){ cin>>a; if(d1[a]<3||d1[a]==INF||j[a]==false){ cout<<"?"<<endl; }else{ cout<<d1[a]<<endl; } } } }
end