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) 等价,即每一个二元组只被计算一次。
思路:
根据题目易知,该题是需要求传递闭包。考虑到这里的数据范围非常小,N,M<=500。因此考虑利用Floyd算法的性质来求闭包。先把每一组给出的胜负关系当成图中的一条边,图中各个点为选手,若a战胜b则图中有一条a指向b的边。两点之间只要有边则说明该比赛结果已知。图初始化好了以后,直接使用变形的Floyd即可,所谓变形就是改变其迭代条件,由
dis [ i ][ j ] = min( dis [ i ][ j ], dis [ i ][ k ] + dis [ k ][ j ] )
变为
dis [ j ][ k ] = dis [ j ][ k ] || dis [ j ][ i ] && dis[ i ][ k ]。
待Floyd执行完毕后,统计图中没有边的点的二元组的个数即可。
代码:
#include <stdio.h>
int win[510][510];
int main()
{
int o;
scanf("%d",&o);
for(int v=0;v<o;v++)
{
int n,m;
scanf("%d %d",&n,&m);
for(int i=0;i<m;i++)
{
int a,b;
scanf("%d %d",&a,&b);
win[a][b]=1;
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
if(win[j][i]==0)
continue;
for(int k=1;k<=n;k++)
{
win[j][k]=win[j][k]||win[j][i]&&win[i][k];
}
}
}
int count=0;
for(int i=1;i<n;i++)
for(int j=i+1;j<=n;j++)
{
if(win[i][j]==0&&win[j][i]==0)
count++;
}
printf("%d\n",count);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
win[i][j]=0;
}
}
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 前往喵星机场花费的总时间。
本题不忽略多余的空格和制表符,且每一组答案间要输出一个换行
思路:
这一题如果没有商业线的话那就是单纯的求单源最短路径。因为出现了商业线,因此该题主要有两个思路:1、因为只能乘一条商业线,所以可以枚举商业线,然后求起点到该商业线的距离,商业线到终点的距离,两者加上商业线长度后求得路线总长度,求出最小值,与不经过商业线比最小的即为最短长度。2、采用分层图。可将原图看成一个三维的图,图中只能经过一次捷径,因此原图分为两层,两层都为经济线构成的图,连接两层对应点的为商业线。对该分层图直接跑计算单源最短路算法即可。
这里我采用的是第二种思路,同时使用的是spfa算法,采用记录某一节点上一节点的方式来记录路径,商业线的换乘只要记录换层时的节点即可。
代码:
#include <stdio.h>
#include <string.h>
#include <queue>
#include <vector>
#include <stack>
using namespace std;
#define inf 100000
struct node
{
int num;
int weight;
int flag;
node(int n,int w,int f):num(n),weight(w),flag(f){
}
node(){
}
};
vector<node> map[1010];
int dis[510][2];
int vis[510][2];
int pre[510][2];
queue<int> q;
void init(int s)
{
for(int i=0;i<1010;i++)
map[i].clear();
memset(dis,1,sizeof(dis));
memset(vis,0,sizeof(vis));
memset(pre,0,sizeof(pre));
dis[s][0]=0;dis[s][1]=0;
}
int main()
{
int n,s,e,m,k;
int first=0;
while(scanf("%d %d %d",&n,&s,&e)!=EOF)
{
if(first!=0)
{
printf("\n");
}
first=1;
init(s);
scanf("%d",&m);
for(int i=0;i<m;i++)
{
int x,y,z;
scanf("%d %d %d",&x,&y,&z);
node tmp;
tmp.num=y;tmp.weight=z;tmp.flag=0;
map[x].push_back(tmp);
tmp.num=x;
map[y].push_back(tmp);
tmp.flag=1;
map[y+n].push_back(tmp);
tmp.num=y;
map[x+n].push_back(tmp);
}
scanf("%d",&k);
for(int i=0;i<k;i++)
{
int x,y,z;
scanf("%d %d %d",&x,&y,&z);
node tmp;
tmp.num=y;tmp.weight=z;tmp.flag=1;
map[x].push_back(tmp);
tmp.num=x;
map[y].push_back(tmp);
}
q.push(s);
while(!q.empty())
{
int f=q.front();
q.pop();
int fflag=0;
if(f>n)
fflag=1;
vis[f-fflag*n][fflag]=0;
for(int i=0;i<map[f].size();i++)
{
int onum=map[f][i].num;
int oflag=map[f][i].flag;
int oweight=map[f][i].weight;
if(dis[onum][oflag]>dis[f-fflag*n][fflag]+oweight)
{
dis[onum][oflag]=dis[f-fflag*n][fflag]+oweight;
pre[onum][oflag]=f;
if(vis[onum][oflag]==0)
{
q.push(map[f][i].num+map[f][i].flag*n);
vis[onum][oflag]=1;
}
}
}
}
stack<int> path;
int j=e;int judge=1;int change=0;
if(dis[e][0]>dis[e][1])
j=e+n;
while(j!=s)
{
if(j<=n)
{
if(judge==1)
change=j;
judge=0;
path.push(pre[j][0]);
j=pre[j][0];
}
else
{
if(pre[j-n][1]>n)
path.push(pre[j-n][1]-n);
else
path.push(pre[j-n][1]);
j=pre[j-n][1];
}
}
if(change==0)
change=s;
while(!path.empty())
{
printf("%d ",path.top());
path.pop();
}
printf("%d\n",e);
if(dis[e][0]<=dis[e][1])
printf("Ticket Not Used\n");
else
printf("%d\n",change);
if(dis[e][0]<dis[e][1])
printf("%d\n",dis[e][0]);
else
printf("%d\n",dis[e][1]);
}
}
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 则输出 '?'。
思路:
该题很显然需要在一个带负权甚至带负环的图中求单源最短路径,因此我们无法使用dijktra来求最短路。这里我们使用spfa来求最短路径,但是为了避免负环的影响,我们需要在spfa中加入对负环的判断。这里我采用点的入队次数来判断是否有负环。因为最短路中边数最多为n-1,所以,若一条边入队n次,必定有负环。但因为spfa是点入队列,不容易判断边,因此这里放宽条件采用点入队n次来判断负环。判定负环后,遍历通过该负环可到达的点,打上标记。最后根据标记和dis数组数出对应结果即可。
代码:
#include <stdio.h>
#include <string.h>
#include <queue>
#include <vector>
#define inf 16843009
using namespace std;
int map[210][210];
int dot[210];
int dis[210];
int vis[210];
int cnt[210];
int mark[210];
void initial()
{
memset(map,1,sizeof(map));
memset(dot,0,sizeof(dot));
memset(dis,1,sizeof(dis));
memset(vis,0,sizeof(vis));
memset(cnt,0,sizeof(cnt));
memset(mark,0,sizeof(mark));
dis[1]=0;
cnt[1]=1;
}
void dfs(int a,int n)
{
if(vis[a]==0||mark[a]==0)
{
vis[a]=1;
mark[a]=1;
for(int i=1;i<=n;i++)
{
if(map[a][i]!=inf)
{
dfs(i,n);
}
}
}
}
void spfa(int n)
{
queue<int> q;
vis[1]=1;
q.push(1);
while(!q.empty())
{
int f=q.front();
q.pop();
vis[f]=0;
for(int i=1;i<=200;i++)
{
if(map[f][i]!=inf)
{
if(dis[i]>dis[f]+map[f][i])
{
dis[i]=dis[f]+map[f][i];
if(cnt[i]>n)
{
dfs(i,n);
}
if(vis[i]==0)
{
q.push(i);
vis[i]=1;
cnt[i]++;
}
}
}
}
}
}
int main()
{
// freopen("data.in","r",stdin);
// freopen("data.out","w",stdout);
int t;
scanf("%d",&t);
for(int i=0;i<t;i++)
{
initial();
int n,m,q;
scanf("%d",&n);
for(int j=1;j<=n;j++)
{
scanf("%d",&dot[j]);
}
scanf("%d",&m);
for(int j=1;j<=m;j++)
{
int a,b;
scanf("%d %d",&a,&b);
map[a][b]=(dot[b]-dot[a])*(dot[b]-dot[a])*(dot[b]-dot[a]);
}
spfa(n);
for(int j=1;j<=n;j++)
{
if(mark[j]==1)
{
dis[j]=-1;
}
}
scanf("%d",&q);
printf("Case %d:\n",i+1);
for(int j=0;j<q;j++)
{
int p;
scanf("%d",&p);
if(dis[p]==-1||dis[p]<3||dis[p]==inf)
printf("?\n");
else
printf("%d\n",dis[p]);
}
}
}