A - TT 的魔法猫
众所周知,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
解题思路:
首先,对于这道题目,题意比较简单,这种传递性的关系我们从中也能容易想到图的性质,关于图的算法很多,但是显然这道题目要求查看每两个点之间是否有一条“路径”,在这道题目中,无所谓路径的长短,我们只关心两点是否连通,那么利用floy算法便可以解决这道题目,因为floy算法在解决各个源最短路径上有着一定的优越性,不仅是算法复杂度方面的,也有代码实现方面的,因为只需要三重循环。不过这道题在时间方面有着一定的限制,所以我们要进行剪枝处理,我们会发现最内层循环中有一个变量只涉及到外两层循环的变量,所以我们可以针对其进行剪枝。另外,便是我们对于floy算法的理解,这个算法来源于动态规划思想,但由于时间复杂度方面的考虑,我们将其提取为数组实现的迭代算法,但在元素值的来回变化之间,好像没有一个固定的元素可以作为参考标准,这使我们再次利用动态规划思想进行理解是产生一些困难。在这里我们不如将这个算法理解为一种对最短路径必定存在的事实的一种逐渐逼近,也就是说,我们不再从点与点的关系去考虑问题,而是随便抽象出一条最短路径来,我们直接从要求解的结果出发好比说我们不再纠结于怎么做,而是上升一个层次研究可以直接做什么),研究最短路径的共性,从中研究出一种必定能实现的统一算法,比如说,我们在外层循环时,任意一条路径假如含有k节点,那么我在更新时,该小段的最短则已经实现,在逐渐的拓扑过程中,每一条最短路径最终都会实现,不过,这其中也隐含着一个道理,如果一条最短路径中含有某两个点,则这两个点中间的子路径体现在任何一条最短路径上都是相同的,这也是该算法对于唯一存在性的一种要求体现。
#include<iostream>
#include<stdio.h>
#include<string.h>
using namespace std;
int dis[501][501]={0};
int main()
{
int label;
scanf("%d",&label);
int N,M;
int begin,end;
while(label--)
{
scanf("%d%d",&N,&M);
memset(dis,0,sizeof(dis));
for(int i=0;i<M;i++)
{
scanf("%d%d",&begin,&end);
dis[begin][end]=1;
}
for(int k=1;k<=N;k++)
{
for(int i=1;i<=N;i++)
{
for(int j=1;j<=N;j++)
{
if(dis[i][k]==0)
break;
if(dis[i][j]==0&&(dis[i][k]&dis[k][j]==1))
dis[i][j]=1;
}
}
}
int ans=0;
for(int i=1;i<=N;i++)
{
for(int j=1;j<i;j++)
{
if(dis[i][j]!=1&&dis[j][i]!=1)
ans++;
}
}
cout<<ans<<endl;
}
}在这里插入代码片
B - TT 的旅行日记
众所周知,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
解题思路:
感觉这道题目比较新,新在其思想,主要是自己之前没有接触过,分层图的概念使得我们处理一些在常规问题中添加特殊要求的题目在思维上有一个更好的扩展,这使得我们不用单纯的从条件上对其进行限制,而是退一步将其再次化为我们习惯的常规问题,所以说,这是一种思维方式,也和我们之前做过的一道题中添加超级源点的思维有些相似,这有一种扩充拓扑的思想在其中,值得我们去思考。接下来便是对于迪杰斯特拉算法的常规应用,dj算法比较经典,所以其部分代码我们应当背记。另外,便是对其思想的思考,其和prim最小生成树算法有些相似,核心都体现这边加点边更新的逻辑,这种处理方式使得时间复杂度直接从n3降到n2,其中的贪心思想与处理技巧对于我们处理一系列问题有着一定的启示作用。
#include<iostream>
#include<stdio.h>
#include<string.h>
#include<queue>
#include<vector>
using namespace std;
struct Edge {
int to,value,next;
Edge() {
}
Edge(int _to,int _value,int _next) {
to=_to;
value=_value;
next=_next;
}
} edge[6006];
int head[1002];
int tot;
void init() {
tot=1;
memset(head,-1,sizeof(head));
}
void add(int begin,int end,int value) {
edge[tot].next=head[begin];
edge[tot].to=end;
edge[tot].value=value;
head[begin]=tot++;
}
int N,S,E,M,K;
int start,finish,val;
int dis[1002];
int pre[1002];
bool label[1002];
priority_queue<pair<int,int> > qu;
vector<int> vec;
int stage=0;
void dj(int root) {
while(qu.size())qu.pop();
memset(pre,0,sizeof(pre));
memset(dis,0xf,sizeof(dis));
memset(label,0,sizeof(label));
qu.push(make_pair(0,root));
dis[root]=0;
while(!qu.empty()) {
int point=qu.top().second;
qu.pop();
if(label[point]) continue;
label[point]=1;
for(int i=head[point]; i!=-1; i=edge[i].next) {
int cc=edge[i].to,dd=edge[i].value;
if(dis[cc]>dis[point]+dd) {
dis[cc]=dis[point]+dd;
pre[cc]=point;
qu.push(make_pair(-dis[cc],cc));
}
}
}
}
int qq=0;
int main() {
while(scanf("%d%d%d",&N,&S,&E)!=EOF) {
scanf("%d",&M);
init();
stage=0;
vec.clear();
for(int i=0; i<M; i++) {
scanf("%d%d%d",&start,&finish,&val);
add(start,finish,val);
add(finish,start,val);
add(start+N,finish+N,val);
add(finish+N,start+N,val);
}
scanf("%d",&K);
for(int i=0; i<K; i++) {
scanf("%d%d%d",&start,&finish,&val);
add(start,finish+N,val);
add(finish,start+N,val);
}
dj(S);
int T;
if(dis[E]>dis[E+N])
T=E+N;
else
T=E;
while(T!=0) {
if(T>N)
stage=T;
vec.push_back(T);
T=pre[T];
}
if(qq>0)
{
cout<<endl;
}
qq++;
for(int i=vec.size()-1; i>0; i--) {
if(vec[i]>N)
cout<<vec[i]-N<<" ";
else
cout<<vec[i]<<" ";
}
if(vec[0]>N)
cout<<vec[0]-N<<endl;
else
cout<<vec[0]<<endl;
if(stage==0) {
cout<<"Ticket Not Used"<<endl;
cout<<dis[E]<<endl;
} else {
cout<<pre[stage]<<endl;
cout<<dis[E+N]<<endl;
}
}
}在这里插入代码片
C - TT 的美梦
这一晚,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:
?
?
解题思路:
很显然,这也是一道求最短路的题目,也很容易知道这其中有可能存在负边,而对于处理这种情况,我们应当首选spfa算法,但这对于这道题目来讲还不够,图中有可能存在负环,因为题目中要求税费小于3的情况下直接输出?,也就是说我们不需要对负环中的点以及负环中的点所能到达的点进行考虑。对于这种情况,我们只需要利用dfs加一个标记数组进行标记即可,接着便可以继续跑spfa算法了;而对于spfa算法,其是对bellman_ford算法的一种改进,而bellman_ford算法正是动态规划思想的迭代求解,这与floy算法有些类似,我们在理解它的时候也可以对比关联着进行理解,我们都可以从路径真实存在性,以及算法中路径的逐渐逼近过程中总会找着正解中进行理解思考。所以说,spfa中设置队列,我们完全可以替换容器,利用一个栈进行存储也可以,在该算法中,容器仅仅起到了存储的作用,但也不乏队列在概率意义上会有时间上更优越一点的性能的可能性。
#include<iostream>
#include<stdio.h>
#include<string.h>
#include<queue>
#include<cmath>
#include<stack>
using namespace std;
int T,N,M,Q,P;
struct Edge{
int to,value,next;
Edge(){
}
Edge(int _to,int _value,int _next)
{
to=_to;
value=_value;
next=_next;
}
}edge[100005];
int tot;
int head[205];
void init(int n)
{
tot=0;
for(int i=0;i<=n;i++)
{
head[i]=-1;
}
}
void add(int begin,int end,int value)
{
edge[tot].next=head[begin];
edge[tot].to=end;
edge[tot].value=value;
head[begin]=tot++;
}
int cnt[205];//表示入队列次数;
bool label[205];//表示是否在负环中;
int dis[205];//表示距离;
int isin[205];
int point[205];
int beg,en;
//queue<int> qu;
stack<int> qu;
void dfs(int start)
{
label[start]=1;
for(int i=head[start];i!=-1;i=edge[i].next)
{
if(label[edge[i].to]==0)
dfs(edge[i].to);
}
}
void spfa(int start)
{
dis[start]=0;
cnt[start]=0;
int func;
qu.push(start);
while(qu.size())
{
func=qu.top();
qu.pop();
if(label[func])continue;
isin[func]=0;
for(int i=head[func];i!=-1;i=edge[i].next)
{
if(dis[func]+edge[i].value<dis[edge[i].to])
{
dis[edge[i].to]=dis[func]+edge[i].value;
if(!isin[edge[i].to])
{
if(label[edge[i].to]==0)
{
qu.push(edge[i].to);
isin[edge[i].to]=1;
cnt[edge[i].to]=cnt[func]+1;
if(cnt[edge[i].to]>=N)
dfs(edge[i].to);
}
}
}
}
}
}
int main()
{
scanf("%d",&T);
for(int j=1;j<=T;j++)
{
while(qu.size()) qu.pop();
memset(isin,0,sizeof(isin));
memset(cnt,0,sizeof(cnt));
memset(label,0,sizeof(label));
for(int i=0;i<205;i++)dis[i]=10000000;
memset(point,0,sizeof(point));
scanf("%d",&N);
init(N);
for(int i=1;i<=N;i++)
{
scanf("%d",&point[i]);
}
scanf("%d",&M);
for(int i=0;i<M;i++)
{
scanf("%d%d",&beg,&en);
add(beg,en,pow(point[en]-point[beg],3));
}
spfa(1);
scanf("%d",&Q);
cout<<"Case "<<j<<":"<<endl;
for(int i=0;i<Q;i++)
{
scanf("%d",&P);
if(label[P]==1||dis[P]<3||dis[P]==10000000)
cout<<"?"<<endl;
else
cout<<dis[P]<<endl;
}
}
} 在这里插入代码片