目录
A.[floyd]TT的魔法猫
题意
给定一个胜负关系表,上面包含N个人和M个胜负关系,每个胜负关系A B表示A胜过B,且胜负关系具有传递性,已知每两人之间都要进行一次比赛,问有多少场比赛的胜负无法预知
样例
样例输入:
多组数据,第一行给出数据组数
对每一组数据 第一行给出 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
样例输出:
对于每一组数据,输出一个数字表示有多少场比赛的胜负不能被预知
0
0
4
思路
1.三次循环进行floyd, relation[i][j]=1仅当r[i][k]=1&&r[k][j]=1
2.用全部的比赛场数减去不能被确定胜负的场次。某一场比赛不能被确定胜负,当且仅当其r[i][j]=0&&r[j][i]=0 (有一个等于一 就代表能够判断出胜负)
总结
1.由于胜负关系具有传递性,且观察数据范围较小,故可以采用floyd的方法,将题目抽象成一张图,点与点之间的联通则代表胜负关系(i->j表示i胜过j)
2.floyd算法的时间复杂度为O(n^3),500500500*数据组数的数据大小在不进行任何优化时会T,故进行剪枝,即在第二层循环时,如果r[i][k]=0 则continue(因为条件要求都为1 此时已经不能判断出胜负了)
代码
#include<iostream>
#include<stdio.h>
#include<algorithm>
#include<math.h>
#include<string.h>
int n,m,T;
int relation[510][510];
using namespace std;
int main()
{
cin>>T;
while(T--)
{
cin>>n>>m;
memset(relation,0,sizeof(relation));
int a,b;
for(int i=1;i<=m;i++)
{
cin>>a>>b;
relation[a][b]=1;
}
for(int k=1;k<=n;k++)
for(int i=1;i<=n;i++)
{
if(!relation[i][k]) continue;
for(int j=1;j<=n;j++)
if(!relation[i][j]&&relation[i][k]&&relation[k][j]) relation[i][j]=1;
}
int ans=n*(n-1)/2;
for(int i=1;i<=n;i++)
for(int j=i+1;j<=n;j++)
if(relation[i][j]||relation[j][i]) ans--;
cout<<ans<<endl;
}
system("pause");
return 0;
}
B.[有条件的SPFA]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),表示可以乘坐经济线在车站 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
样例输出:
对于每组数据,输出3行。第一行按访问顺序给出经过的各个车站(包括起点和终点),第二行是换乘商业线的车站编号(如果没有使用商业线车票,输出"Ticket Not Used",不含引号),第三行是前往终点花费的总时间。
本题不忽略多余的空格和制表符,且每一组答案间要输出一个换行!!!
思路
1.用经济线初始化图。从起点、终点分别跑一遍SPFA,SPFA里要记录每个点的前驱,并且要把跑完的两个最短距离数组与前驱数组都保留住
2.对于每一条商业线(例: s t w即从s到t须花费w),边读入边处理,判断当前最短距离(初始时即为起点到终点的距离) 与 起点到s的距离+t到终点的距离+w
以及 起点到t的距离+s到终点的距离+w
的大小,及时更新
3.如果用了商业线,就从s到起点进行递归回溯路径,并且t到终点递归回溯路径(记得输出空格!!!),没用的话只需要从终点到起点递归回溯路径
总结
1.因为只能通过一条商业线,我们的最初想法是进行n次单源最短路,但是不用想肯定会T
2.于是我们可以分别从起点、终点跑一遍最短路,随后对每一个商业线,判断加上这条商业线会不会使最短路径缩小,最后与不走任何商业线的情况进行一次比较
3.这道题的输出格式也太奇怪了吧!!!!!!!!!!!!为啥样例输出的路径没有空格,但是却要求空格啊!!!为啥每组输出之间还需要再加一空行啊!!!为啥啊为啥啊为啥啊!!!(无能狂怒)
4.如果是多条商业线,也可以记录dis[i][0]与dis[i][1] (还可以有dis[i][2·3·4·5···]),第二维表示当前已走了多少条商业线,如果是0则可以到0 也可以到1,而如果是1的话只能再到1了(因为已经走过一条商业线了) 类似于一种动态规划的思想
代码
#include<iostream>
#include<stdio.h>
#include<algorithm>
#include<math.h>
#include<string.h>
#include<queue>
#include<utility>
#include<vector>
#define MAXN 1010
#define MAXM 5010
#define INF 0xfffff
using namespace std;
int N,S,E,M,K;
int h[MAXN],tnt,minl;
int dis[MAXN],vis[MAXN],pre[MAXN];
int diss[MAXN],dist[MAXN],pres[MAXN],pret[MAXN];
vector<int> path;
struct edge
{
int from,to,w,nxt;
}Edge[MAXM];
void add(int u,int v,int w)
{
Edge[++tnt].to=v;
Edge[tnt].from=u;
Edge[tnt].w=w;
Edge[tnt].nxt=h[u];
h[u]=tnt;
}
/*
priority_queue < pair<int,int> > q;
int dijkstra(int s,int t)//地杰斯特拉 返回s到t的最短距离
{
while(!q.empty()) q.pop();//先清空小根堆
int dis[MAXN],vis[MAXN];
memset(dis,INF,sizeof(dis));
memset(vis,0,sizeof(vis));
dis[s]=0;
q.push(make_pair(dis[s],s));
while(!q.empty())
{
int now=q.top().second; q.pop();
if(vis[now]) continue;
vis[now]=true;
for(int i=h[now];i!=-1;i=Edge[i].nxt)
{
int v=Edge[i].to,w=Edge[i].w;
if(dis[v]>dis[now]+w)
{
dis[v]=dis[now]+w;
q.push(make_pair(-dis[v],v));
}
}
}
return dis[t];
}
*/
queue<int> q;
void SPFA(int s)
{
while(!q.empty()) q.pop();//先清空队列
for(int i=1;i<=N;i++) dis[i]=INF,vis[i]=0;
for(int i=1;i<=N;i++) pre[i]=-1;
dis[s]=0,vis[s]=1;pre[s]=-1;
q.push(s);
while(!q.empty())
{
int now=q.front();q.pop();
vis[now]=0;
for(int i=h[now];i!=-1;i=Edge[i].nxt)
{
int v=Edge[i].to;
if(dis[v]>dis[now]+Edge[i].w)
{
dis[v]=dis[now]+Edge[i].w;
pre[v]=now;
if(!vis[v]) {vis[v]=1;q.push(v);}
}
}
}
}
void printS(int k)
{
if(k==S) {printf("%d",S);;return;}
printS(pres[k]);
printf(" %d",k);
}
void printE(int k)
{
if(k==E) {printf(" %d", E);return;}
printf(" %d", k);
printE(pret[k]);
}
int main()
{
int f=0;
while(~scanf("%d%d%d", &N, &S, &E))
{
if(f++) printf("\n");
path.clear();
int flag=-1;//记录选了哪个商业线
memset(h,-1,sizeof(h));//初始化
tnt=0;
cin>>M;
int x,y,z;
for(int i=1;i<=M;i++){
cin>>x>>y>>z;
add(x,y,z);
add(y,x,z);
}
SPFA(S);
memcpy(diss,dis,sizeof(dis));
memcpy(pres,pre,sizeof(pre));
SPFA(E);
memcpy(dist,dis,sizeof(dis));
memcpy(pret,pre,sizeof(pre));
int ans=diss[E];//不用商务票
int s=-1,t=-1;
cin>>K;
for(int i=1;i<=K;i++)
{
cin>>x>>y>>z;
if(ans>z+diss[x]+dist[y])// S->x->y->E
{
ans=z+diss[x]+dist[y];
s=x;t=y;
}
if(ans>z+diss[y]+dist[x])// S->y->x->E
{
ans=z+diss[y]+dist[x];
s=y;t=x;
}
}
if(s==-1)//没用商务票
{
printS(E);
printf("\nTicket Not Used\n");
}
else
{
printS(s);
printE(t);
printf("\n%d\n", s);
}
printf("%d\n", ans);
}
system("pause");
return 0;
}
C.TT的美梦
题意
有1~N共N个城市,1号城市是首都,每个城市有一个繁华程度ai,如果从城市i走到城市j,需要交纳(a[j]-a[i])^3的费用。城市间共有M条道路,现给出这些条道路,求从首都到每个城市的最小费用(如果无法到达或者费用小于3 输出’?’)
样例
样例输入:
第一行输入 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
样例输出:
每个询问输出一行,如果不可达或费用小于 3 则输出 ‘?’。
Case 1:
3
4
Case 2:
?
?
思路
1.根据给出的边,以及每个城市的繁华程度初始化图,并以节点1为源点跑一遍SPFA
2.注意SPFA时不再用记录前驱,而是cnt[]数组记录每个点入队的次数 如果某个点入队超过n次,则说明他处于一个负环中,我们给这个点打上标记并且以他为源点进行BFS,并把能到达的点都标记上(因为如果处于负环中,就出不来了 相当于无法到达)
3.对于不能到达的点(dis[i]==INF
或minu[i]==true
)以及费用小于3的点(dis[i]<3
),输出?
注意对每组数据,要先输出 Case i:\n
!!!
总结
1.由于繁华程度不同,故可能出现负权边,进而可能会出现负环,于是我们采用SPFA来解决负环问题
2.SPFA判负环:根据某些厉害的定理,SPFA松弛点时,点不可能被松弛n次(即如果一个点入队了n次,那么这个点就在一个负环中)
3.尽管样例输出中 输出了Case 1 2···,但是为啥题目描述中没说嘞!!!(又多de了一个小时bug 哭 神坑在此)
代码
#include<iostream>
#include<stdio.h>
#include<algorithm>
#include<math.h>
#include<string.h>
#include<queue>
#define MAXN 1010
#define MAXM 100010
#define INF 0xffffff
int N,M,a[MAXN];
int tnt,h[MAXN];
using namespace std;
struct edge
{
int from,to,nxt,w;
}e[MAXM];
void add(int u,int v,int w)
{
e[++tnt].from=u;
e[tnt].to=v;
e[tnt].w=w;
e[tnt].nxt=h[u];
h[u]=tnt;
}
int vis[MAXN],dis[MAXN],cnt[MAXN];
int minu[MAXN];
queue <int> q;
void bfs(int s)
{
queue<int> p;
p.push(s);
while(!p.empty())
{
int now=p.front();p.pop();
for(int i=h[now];i!=-1;i=e[i].nxt)
{
int v=e[i].to;
if(minu[v]) continue;
minu[v]=true;
p.push(v);
}
}
}
void SPFA(int s)
{
for(int i=1;i<=N;i++) {dis[i]=INF;vis[i]=false;cnt[i]=0;}
dis[s]=0;vis[s]=1;//vis表示点是否在队列中
q.push(s);
while(!q.empty())
{
int now=q.front();q.pop();
if(minu[now]) continue;//处在一个负环中 则直接跳过
vis[now]=0;
for(int i=h[now];i!=-1;i=e[i].nxt)
{
int v=e[i].to;
if(dis[v]>dis[now]+e[i].w)
{
dis[v]=dis[now]+e[i].w;
cnt[v]=cnt[now]+1;
if(cnt[v]>=N) //负环
{
minu[v]=true;
bfs(v);
}
if(!vis[v]) {vis[v]=1;q.push(v);}
}
}
}
}
int main()
{
int T,t=0;
cin>>T;
while(T--)
{
//初始化
t++;
while(!q.empty()) q.pop();
tnt=0;
cin>>N;
for(int i=1;i<=N;i++) {h[i]=-1;minu[i]=false;}
for(int i=1;i<=N;i++) scanf("%d",&a[i]);
cin>>M;
int x,y,z;
for(int i=1;i<=M;i++)
{
cin>>x>>y;
z=(a[y]-a[x])*(a[y]-a[x])*(a[y]-a[x]);//可能存在负边
add(x,y,z);
}
//计算
SPFA(1);
int Q;
cin>>Q;
cout<<"Case "<<t<<":"<<endl;
while(Q--)
{
int P;
cin>>P;
if(minu[P]||dis[P]==INF||dis[P]<3) cout<<'?'<<endl;
else cout<<dis[P]<<endl;
}
}
return 0;
}