A - TT 的魔法猫
题意
众所周知,TT 有一只魔法猫。
这一天,TT 正在专心致志地玩《猫和老鼠》游戏,然而比赛还没开始,聪明的魔法猫便告诉了 TT 比赛的最终结果。TT 非常诧异,不仅诧异于他的小猫咪居然会说话,更诧异于这可爱的小不点为何有如此魔力?
魔法猫告诉 TT,它其实拥有一张游戏胜负表,上面有 N 个人以及 M 个胜负关系,每个胜负关系为 A B,表示 A 能胜过 B,且胜负关系具有传递性。即 A 胜过 B,B 胜过 C,则 A 也能胜过 C。
TT 不相信他的小猫咪什么比赛都能预测,因此他想知道有多少对选手的胜负无法预先得知,你能帮帮他吗?
思路
由数据规模可知本题可以使用复杂度为O(n3)的弗洛伊德算法思想,在原算法的基础上将松弛过程修改为传递闭包,实现对胜负关系的传递,代码与原始算法几乎一样简洁。
总结
本题的程序主体比较简单,但直接使用弗洛伊德会超时,在优化过程中考虑到剪枝。在第二层循环中,当传递闭包的前项已不能满足传递需求,即a[i][k]≠1时,可以直接不进入第三层循环,如此剪枝后即可通过。
代码
#include<iostream>
using namespace std;
bool a[505][505];
int n,N,M;
int main(){
cin>>n;
for(int t=1;t<=n;t++){
cin>>N>>M;
int A,B;
for(int i=1;i<=M;i++){
cin>>A>>B;
a[A][B]=1;
}
//弗洛伊德传递闭包
for(int k=1;k<=N;k++){
for(int i=1;i<=N;i++){
if(a[i][k]){//剪枝
for(int j=1;j<=N;j++)
if(a[k][j])a[i][j]=1;
}
}
}
//计数
int cot=0;
for(int i=1;i<=N;i++){
for(int j=1;j<=N;j++){
if(a[i][j])cot++;
a[i][j]=0;
}
}
int ans=N*(N-1)/2-cot;
cout<<ans<<endl;
}
}
B - TT 的旅行日记
题意
众所周知,TT 有一只魔法猫。
今天他在 B 站上开启了一次旅行直播,记录他与魔法猫在喵星旅游时的奇遇。 TT 从家里出发,准备乘坐猫猫快线前往喵星机场。猫猫快线分为经济线和商业线两种,它们的速度与价钱都不同。当然啦,商业线要比经济线贵,TT 平常只能坐经济线,但是今天 TT 的魔法猫变出了一张商业线车票,可以坐一站商业线。假设 TT 换乘的时间忽略不计,请你帮 TT 找到一条去喵星机场最快的线路,不然就要误机了!
思路
本题是一个单源最短路的变式应用。在以经济线为边的图上,分别以起点和终点为源进行复杂度为O((n+m)logn)的Dijstra算法来计算出起点和终点到达每个点的最短路,随后枚举所有的商业线,对每一条商业线(i,j),将从i上线与从j上线的两种乘车方式使用时间比较,最后再与不乘坐商业线比较,在O(k)复杂度内就可以比较出最短路径。
总结
本题对输出有一定要求。在书写完主程序后,要利用前驱结点进行倒序输出,因为思绪不清反复修改,花费了相当的时间。除此之外,因为读题不清,在空行上被卡了多次PE。
debug:Dijstra算法中使用到了一个预编译的无穷大“INF”,但在设定值时没有考虑到整型的溢出问题,计算过程中INF的两倍超出了整型的最大范围,导致了难以察觉的错误。
代码
#include<iostream>
#include<queue>
#define INF 20000000
using namespace std;
int Num,S,E,M,K;
int x,y,z;
int reach[505];//访问标记
int parent1[505];//前驱节点-起点开始
int parent2[505]; //前驱结点-终点开始
int dis1[505];//距离-起点开始
int dis2[505];//距离-终点开始
struct busi{//商业线
int u;
int v;
int w;
void set(int U,int V,int W){
u=U;
v=V;
w=W;
}
};
busi B[1005];
int ans[1005];
struct node{//经济线
int idx;
int weight;
node* next;
};
node* N[505];
void addEdge(int u,int v,int w){
node* p=new node;
p->idx=v;
p->weight=w;
p->next=N[u];
N[u]=p;
node* q=new node;
q->idx=u;
q->weight=w;
q->next=N[v];
N[v]=q;
}
struct Pair{//入队数对
int idx;
int w;
Pair(int I=0,int W=0){idx=I;w=W;}
void operator=(const Pair& p){idx=p.idx;w=p.w;}
bool operator<(const Pair& p)const{return w<p.w;}
bool operator>(const Pair& p)const{return p<*this;}
bool operator<=(const Pair& p)const{return !(p<*this);}
bool operator>=(const Pair& p)const{return !(*this<p);}
bool operator==(const Pair& p)const{return !(p<*this||*this<p);}
bool operator!=(const Pair& p)const{return p<*this||*this<p;}
};
void clear1(int n){
for(int i=1;i<=n;i++)
reach[i]=0;
}
void clear2(int n){
for(int i=1;i<=n;i++){
dis1[i]=dis2[i]=INF;
parent1[i]=parent2[i]=0;
node* p=N[i];
node* q;
while(p!=NULL){
q=p->next;
delete p;
p=q;
}
N[i]=NULL;
}
}
priority_queue<Pair,vector<Pair>,greater<Pair> > Q;
void solve(int sta,int* dis,int* par){//dijstra
dis[sta]=0;
Pair temp0;
temp0.idx=sta;
temp0.w=dis[sta];
Q.push(temp0);
while(!Q.empty()){
Pair temp;
temp=Q.top();
Q.pop();
if(reach[temp.idx]==1)continue;
reach[temp.idx]=1;
node* p=N[temp.idx];
while(p!=NULL){
if(reach[p->idx]==0){
if(dis[p->idx]>dis[temp.idx]+p->weight){
dis[p->idx]=dis[temp.idx]+p->weight;
par[p->idx]=temp.idx;
Pair theNew;
theNew.idx=p->idx;
theNew.w=dis[p->idx];
Q.push(theNew);
}
}
p=p->next;
}
}
}
int main(){
bool jdg=0;
while(cin>>Num){
if(!jdg)jdg=1;
else cout<<endl;
cin>>S>>E;
clear2(Num);
cin>>M;
for(int i=1;i<=M;i++){
cin>>x>>y>>z;
addEdge(x,y,z);
}
cin>>K;
for(int i=1;i<=K;i++){
cin>>x>>y>>z;
B[i].set(x,y,z);
}
clear1(Num);
solve(S,dis1,parent1);
clear1(Num);
solve(E,dis2,parent2);
int ANS=dis1[E];
int jdg1=0;
int jdg2=0;
for(int i=1;i<=K;i++){//穷举用线情况
int a1=dis1[B[i].u]+dis2[B[i].v]+B[i].w;
int a2=dis1[B[i].v]+dis2[B[i].u]+B[i].w;
int tempjdg=0;
if(a1<a2){
ans[i]=a1;
tempjdg=1;
}else{
ans[i]=a2;
tempjdg=2;
}
if(ans[i]<ANS){
ANS=ans[i];
jdg1=i;
jdg2=tempjdg;
}
}
int record[505];
//输出line1
if(jdg1==0){
int cot=0;
int sp=E;
//倒序输出
while(parent1[sp]!=0){
record[cot++]=parent1[sp];
sp=parent1[sp];
}
while(cot!=0)cout<<record[--cot]<<" ";
cout<<E<<endl;
}else{
if(jdg2==1){
int rec1[505];
int cot1=0;
int sp=B[jdg1].u;
//倒序输出
while(parent1[sp]!=0){
rec1[cot1++]=parent1[sp];
sp=parent1[sp];
}
while(cot1!=0)cout<<rec1[--cot1]<<" ";
cout<<B[jdg1].u<<" "<<B[jdg1].v;
sp=B[jdg1].v;
//正序输出
while(parent2[sp]!=0){
sp=parent2[sp];
cout<<" "<<sp;
}
cout<<endl;
}else{
int rec1[505];
int cot1=0;
int sp=B[jdg1].v;
//倒序输出
while(parent1[sp]!=0){
rec1[cot1++]=parent1[sp];
sp=parent1[sp];
}
while(cot1!=0)cout<<rec1[--cot1]<<" ";
cout<<B[jdg1].v<<" "<<B[jdg1].u;
sp=B[jdg1].u;
//正序输出
while(parent2[sp]!=0){
sp=parent2[sp];
cout<<" "<<sp;
}
cout<<endl;
}
}
//输出line2
if(jdg1==0)cout<<"Ticket Not Used"<<endl;
else if(jdg2==1)cout<<B[jdg1].u<<endl;
else if(jdg2==2)cout<<B[jdg1].v<<endl;
//输出line3
cout<<ANS<<endl;
}
}
C - TT 的美梦
题意
这一晚,TT 做了个美梦!
在梦中,TT 的愿望成真了,他成为了喵星的统领!喵星上有 N 个商业城市,编号 1 ~ N,其中 1 号城市是 TT 所在的城市,即首都。
喵星上共有 M 条有向道路供商业城市相互往来。但是随着喵星商业的日渐繁荣,有些道路变得非常拥挤。正在 TT 为之苦恼之时,他的魔法小猫咪提出了一个解决方案!TT 欣然接受并针对该方案颁布了一项新的政策。
具体政策如下:对每一个商业城市标记一个正整数,表示其繁荣程度,当每一只喵沿道路从一个商业城市走到另一个商业城市时,TT 都会收取它们(目的地繁荣程度 - 出发地繁荣程度)^ 3 的税。
TT 打算测试一下这项政策是否合理,因此他想知道从首都出发,走到其他城市至少要交多少的税,如果总金额小于 3 或者无法到达请悄咪咪地打出 ‘?’。
思路
本题依然是一个求最短路的题,但因为负边的存在,弗洛伊德和Dijstra算法无法满足需求,因而使用了堆优化的贝尔曼福德算法(SPFA)。
原始的贝尔曼福德算法在第n轮中确定每条最短路的第n条边,这个过程中有大量的冗余操作。观察到每轮的有效操作实际上只发生在与上一轮操作节点相邻的节点上,为此,利用堆进行类层次遍历的操作过程,极大优化了时间效率。
为了解决负环的问题,使用了一个简化的判断:当一条路走过n条边后权值和仍能减小时,必为负环。在每轮入堆操作前进行该判断,即可找出负环。
总结
花费一定时间理解了算法原理后,程序书写起来不难。遇到了一个使答案错误的考虑不足:当找到负环时,不能单纯地将当前点标记并且不入堆,这样在这条路上堆循环不会继续,其他点就没有了被标记的可能。实际上当一个点具有到达原点的负值路径时,整个回路上的所有点都必然也拥有到达原点的负值路径;正确做法是从找出的这个点开始,利用一次遍历搜索将所有连通的点全部标记上。
debug:没有注意到样例输出中有提示语“Case x:”,这个提示语并没有出现在题目的输出描述中,导致答案错误。
代码
#include<iostream>
#include<queue>
#define INF 2000000000
using namespace std;
int T,N,M,Q;
int a[205];
int dis[205];//距离
int pre[205];//前驱节点
int inq[205];//入队标记
int cnt[205];//路径边计数(用于判断负环)
int reach[205];//访问标记
struct node{
int idx;
int weight;
node* next;
};
node* Node[205];
void insert(int u,int v){//给图插入边
node* p=new node;
p->idx=v;
p->weight=(a[v]-a[u])*(a[v]-a[u])*(a[v]-a[u]);
p->next=Node[u];
Node[u]=p;
}
void tag(int v){//当一个点被标记为有负路,dfs为所有连通的点标记上
node* p=Node[v];
while(p!=NULL){
if(reach[p->idx]!=-1){
reach[p->idx]=-1;
tag(p->idx);
}
p=p->next;
}
}
queue<int> q;
void SPFA(){//堆优化的贝尔曼福德
for(int i=1;i<=N;i++){//初始化
dis[i]=INF;
pre[i]=0;
inq[i]=0;
cnt[i]=0;
reach[i]=0;
}
dis[1]=0;
inq[1]=1;
q.push(1);
while(!q.empty()){//堆循环
int u=q.front();
q.pop();
if(reach[u]!=-1)reach[u]=1;
inq[u]=0;
node* p=Node[u];
while(p!=NULL){//处理所有相邻的点
int v=p->idx;
reach[v]=1;
if(dis[v]>dis[u]+p->weight){//判断松弛
cnt[v]=cnt[u]+1;
if(cnt[v]>=N){//判断负环
reach[v]=-1;
tag(v);
}
dis[v]=dis[u]+p->weight;
pre[v]=u;
if(!inq[v]&&reach[v]!=-1){//判断入堆
q.push(v);
inq[v]=1;
}
}
p=p->next;
}
}
}
int main(){
cin>>T;
for(int k=1;k<=T;k++){
cin>>N;
for(int i=1;i<=N;i++)cin>>a[i];
cin>>M;
int A,B;
for(int i=1;i<=N;i++){//初始化邻接链表
node*p=Node[i];
node*q;
while(p!=NULL){
q=p->next;
delete p;
p=q;
}
Node[i]=NULL;
}
for(int i=1;i<=M;i++){//输入
cin>>A>>B;
insert(A,B);
}
SPFA();
cout<<"Case "<<k<<":"<<endl;//遗漏输出
cin>>Q;
int P;
for(int i=1;i<=Q;i++){
cin>>P;
if(reach[P]<=0)cout<<"?"<<endl;
else if(dis[P]<3)cout<<"?"<<endl;
else cout<<dis[P]<<endl;
}
}
}