山东大学程序设计思维学习笔记作业7

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

分析:写这篇文章的时候学完了拓扑排序,自然联想在一起,但这里是判断胜负关系,拓扑排序显然毫不相关。可以看作有向图,如果两者之间有胜负关系,则有一条边。无法判断胜负关系有两种情况,一是环,二是不可达。这里幸运的是不需要找到环(当时都没想到这点,代码也是直接忽略了)。至于不可达,修改floyd算法,把求最小值改成求dis[i][j]和dis[i][k]&w(k,j)的最大值即可。

代码

//
// Created by haofeng on 4/1/20.
//

#include <iostream>
#include <algorithm>
#include <stdio.h>

using namespace std;
int N,M,G;
int dis[510][510];//floyd用
void init(int n){
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            dis[i][j]=0;
}
void floyd(int n){
    for(int k=1;k<=n;k++)
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
            {
                dis[i][j]=max(dis[i][j],dis[i][k]&dis[k][j]);
                if(dis[i][k]==0)break;//剩下&都是0,没必要继续遍历j,dis[i][j]保持不变即可
            }
}
int main(){
    scanf("%d",&G);
    while (G>0){
        G--;
        scanf("%d %d",&N,&M);
        init(N);
        int u,v;
        for(int i=1;i<=M;i++){
            scanf("%d %d",&u,&v);
            dis[u][v]=1;
        }
        floyd(N);
        int total=0;
        for(int i=1;i<=N;i++){
            for(int j=i+1;j<=N;j++){//遍历上三角
                if(dis[i][j]==0&&dis[j][i]==0)total++;
            }
        }
        cout<<total<<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 前往喵星机场花费的总时间。

本题不忽略多余的空格和制表符,且每一组答案间要输出一个换行

sample input

4 1 4
4
1 2 2
1 3 3
2 4 4
3 4 5
1
2 4 3

sample output

1 2 4
2
5

分析:可以枚举商业线,然后加入到经济线中,每次做单源最短路。还好迪克斯特拉的复杂度不高,这个题没问题。但这个题实际上有更普适的方法,分层图的做法,不过我不会…

//
// Created by haofeng on 4/2/20.
//
#include <iostream>
#include <algorithm>
#include <queue>
#include <stack>
#include <stdio.h>

using namespace std;
int N,S,E,M,K,C=0;
struct edge{
    int v,u,w,next;//c是商业线commerical
    bool operator < (const edge& e)const{
        return this->w>e.w;
        //因为下面是最大堆
    }
}Edges[2010];
int head[510],tot;
int disS[510];
int disE[510];//dijstrala
int preS[510];
int preE[510];
int visit[510];
bool com=false;
priority_queue<edge> heap;

void init(int n){
    tot=0;com=false;
    for(int i=0;i<=n;i++){head[i]=-1;preS[i]=preE[i]=0;disE[i]=disS[i]=99999999;visit[i]=0;}
}
void addedge(int v,int u,int w){
    tot++;
    Edges[tot].v=v;
    Edges[tot].u=u;
    Edges[tot].w=w;
    Edges[tot].next=head[v];
    head[v]=tot;
}
void dijkstra(int s,int pre[],int dis[]){
    while(!heap.empty())heap.pop();
    for(int i=0;i<=N;i++){
        visit[i]=0;
    }
    dis[s]=0;
    pre[s]=-1;
    heap.push({s,s,0,-1});
    while(!heap.empty()){
        int v=heap.top().v;
        heap.pop();
        if(visit[v])continue;//剪枝吧,不做也没事
        visit[v]=1;
        for(int i=head[v];i!=-1;i=Edges[i].next){
            int w=Edges[i].w;
            if(dis[Edges[i].u]>dis[v]+w){
                dis[Edges[i].u]=dis[v]+w;
                pre[Edges[i].u]=v;
                heap.push({Edges[i].u,s,dis[Edges[i].u],0});
            }
        }
    }

}
void output(int v,int u){
    stack<int> st;
    bool eq=v==u;
    while (preS[v]!=-1){//留S
        st.push(v);
        v=preS[v];
    }
    cout<<S;
    while (!st.empty()){
        printf(" %d",st.top());
        st.pop();
    }
    while (!eq&&preE[u]!=-1){//留E
        printf(" %d",u);
        u=preE[u];
    }
    if(!eq)printf(" %d\n",E);
    else cout<<endl;
}
int main(){
    int cishu=0;
    while (cin>>N>>S>>E){
        init(N);
        cin>>M;
        for(int i=1;i<=M;i++){
            int x,y,z;
            scanf("%d %d %d",&x,&y,&z);
            addedge(x,y,z);
            addedge(y,x,z);
        }
        dijkstra(S,preS,disS);
        dijkstra(E,preE,disE);
        cin>>K;
        int v=-1,u=-1,MIN=disS[E];
        int min_path;
        for(int i=1;i<=K;i++){
            int x,y,z;
            scanf("%d %d %d",&x,&y,&z);
            min_path=disS[x]+disE[y]+z;
            if(MIN>min_path){
                MIN=min_path;
                v=x;u=y;
            }
            swap(x,y);//判断两次
            min_path=disS[x]+disE[y]+z;
            if(MIN>min_path){
                MIN=min_path;
                v=x;u=y;
            }
        }
        if(cishu>0)cout<<endl;
        if(v==-1){
            output(E,E);
            cout<<"Ticket Not Used"<<endl;
        }
        else{
            output(v,u);
            cout<<v<<endl;
        }
        cout<<MIN<<endl;
        cishu++;
    }
}
 

第二种方法,不过最终失败

//
// Created by haofeng on 4/2/20.
//
//这是dis[i][0/1]的思路,但是最后也没搞懂到底错在哪里了,写在这里吧
//主要是什么时候更新没想清楚,dis[i][0]时,会更新1吗?
#include <iostream>
#include <algorithm>
#include <queue>
#include <stack>
#include <stdio.h>

using namespace std;
int N,S,E,M,K,C=0;
struct edge{
    int v,u,w,c,next;//c是商业线commerical
    bool operator < (const edge& e)const{
        return this->w>e.w;
        //因为下面是最大堆
    }
}Edges[2010];
int head[510],tot;
int dis[510][2];//dijstrala
int fore[510];
int visit[510];
bool com=false;
priority_queue<edge> heap;

void init(int n){
    tot=0;com=false;
    for(int i=0;i<=n;i++){head[i]=-1;fore[i]=-1;visit[i]=0;}
}
void addedge(int v,int u,int w,int c){
    tot++;
    Edges[tot].v=v;
    Edges[tot].u=u;
    Edges[tot].w=w;
    Edges[tot].c=c;
    Edges[tot].next=head[v];
    head[v]=tot;
}
void dijkstra(int s){
    while (!heap.empty())heap.pop();
    //第二维0则无商业线
    for(int i=0;i<=N;i++){dis[i][0]=dis[i][1]=99999999;fore[i]=-1;}
    dis[s][0]=dis[s][1]=0;
    fore[s]=0;//序号从1开始,用0做标记没问题
    edge temp{s,s,0,0};
    heap.push(temp);
    while (!heap.empty()){
        int v=heap.top().v;
        heap.pop();
        if(visit[v]==1)continue;
        visit[v]=1;
        for(int i=head[v];i!=-1;i=Edges[i].next){
            //与顶点v邻接的边
            int w=Edges[i].w;
            if(Edges[i].c==1){
                //商业线,只更新dis[v][0]
                if(dis[Edges[i].u][0]>dis[Edges[i].v][0]+w){
                    C=Edges[i].v;
                    dis[Edges[i].u][1]=dis[Edges[i].v][0]+w;
                    fore[Edges[i].u]=Edges[i].v;
                    heap.push({Edges[i].u,S,dis[Edges[i].v][0]+w,1});
                }
            }
            else{
                //都更新,最后取dis[v][0]还是dis[v][1]用一个min做比较
                if(dis[Edges[i].u][0]>dis[Edges[i].v][0]+w){
                    dis[Edges[i].u][0]=dis[Edges[i].v][0]+w;
                    fore[Edges[i].u]=Edges[i].v;
                    heap.push({Edges[i].u,S,dis[Edges[i].v][0]+w,0});
                }
                if(dis[Edges[i].u][1]>dis[Edges[i].v][1]+w){
                    dis[Edges[i].u][1]=dis[Edges[i].v][1]+w;
                    fore[Edges[i].u]=Edges[i].v;
                    heap.push({Edges[i].u,S,dis[Edges[i].v][0]+w,1});
                }
            }
        }
    }

}
int main(){
    while (cin>>N>>S>>E){
        init(N);
        cin>>M;
        for(int i=1;i<=M;i++){
            int x,y,z;
            scanf("%d %d %d",&x,&y,&z);
            addedge(x,y,z,0);
        }
        cin>>K;
        for(int i=1;i<=K;i++){
            int x,y,z;
            scanf("%d %d %d",&x,&y,&z);
            addedge(x,y,z,1);
        }
        dijkstra(S);
        cout<<dis[E][1]<<" "<<dis[E][0]<<endl;
        int a0=dis[E][0];int a1=dis[E][1];
        int min_path=min(dis[E][0],dis[E][1]);
        cout<<min_path<<endl;
        stack<int> q;q.push(E);
        while(fore[E]!=0){
            q.push(fore[E]);
            E=fore[E];
        }
        while (!q.empty()){printf("%d ",q.top());q.pop();}cout<<endl;
        if(C!=0&&min_path==a1){printf("%d\n",C);}else{cout<<"Ticket Not Used"<<endl;}
        cout<<min_path<<endl;
        cout<<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,核心是记录这个点经过了多少次。这里用链式前向星,以后这将是存图较常用的方法。

//
// Created by haofeng on 4/5/20.
//
#include <iostream>
#include <queue>
#include <stdio.h>
#include <math.h>
using namespace std;
struct edge{
    int v,u,w,next;
};
int T,N,M,Q,tot=0;
edge Es[100010];
int head[210];
int a[210];
int inq[210];
int dis[210];
int vis_time[210];
int vis[210];
void init(int n){
    tot=0;
    for(int i=1;i<=n;i++){head[i]=-1;vis[i]=0;}
}
void addedge(int v,int u,int w){
    tot++;
    Es[tot].v=v;
    Es[tot].u=u;
    Es[tot].w=w;
    Es[tot].next=head[v];
    head[v]=tot;

}
void DFS(int s){

    for(int i=head[s];i!=-1;i=Es[i].next)
        if(vis[Es[i].u]==0) {
            vis[Es[i].u] = 1;
            DFS(Es[i].u);
        }
}

void SPFA_Bellman_Ford(int s){
    //计算从s出发到其他点的距离
    for(int i=1;i<=N;i++){dis[i]=99999999;vis_time[i]=0;vis_time[i]=0;inq[i]=0;}
    dis[s]=0;inq[s]=1;
    queue<int> q;q.push(s);
    while (!q.empty()){
        int v=q.front();
        q.pop();
        inq[v]=0;
        for(int i=head[v];i!=-1;i=Es[i].next){
            edge e=Es[i];
            if(dis[e.u]>dis[e.v]+e.w){
                vis_time[e.u]=vis_time[e.v]+1;
                if(vis_time[e.u]>=N){
                   DFS(e.u);inq[e.u]=1;
                }
                dis[e.u]=dis[e.v]+e.w;
                if(!inq[e.u]){
                    q.push(e.u);
                    inq[e.u]=1;
                }
            }
        }
    }
}

int main(){
    cin>>T;int t=0;
    while (t<T){
        t++;cin>>N;
        cout<<"Case "<<t<<":"<<endl;
        int v,u;
        init(N);
        for(int i=1;i<=N;i++){
            scanf("%d",&a[i]);
        }
        cin>>M;
        for(int i=0;i<M;i++){
            scanf("%d %d",&v,&u);
            int w=pow(a[u]-a[v],3);
            addedge(v,u,w);
        }
        SPFA_Bellman_Ford(1);
        cin>>Q;
        for(int j=0;j<Q;j++) {
            int i=0;scanf("%d",&i);
            if (vis[i]==1|| dis[i] == 99999999||dis[i]<3) {
                printf("%s\n", "?");
            } else{
                printf("%d\n",dis[i]);
            }
        }

    }
}
 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值