程序设计作业题 week7

程序设计作业题 week7

to sum up
本周主要学习三个算法,分别是floyd(处理多源最短路径,算法简单,但复杂度为n^3,需要注意三层for循环的顺序问题),dijkstra(处理图中不存在负边单源最短路径,复杂度O((n+m)logn)),SPFA(处理存在负边的单源最短路径,可以用来判断负环)。

一、A题 TT的魔法猫(Floyd)

N个人进行比赛,每两个人有一个胜负关系,该胜负关系可传递(A胜B,B胜C,则可判断A胜C)。先给定M个胜负关系,求还有多少对选手的胜负关系无法确定。

1.Sample input and output

input

第一行给出数据组数。

每组数据第一行给出 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

output

对于每一组数据,判断有多少场比赛的胜负不能预先得知。注意 (a, b) 与 (b, a) 等价,即每一个二元组只被计算一次。

0
0
4

2.解题思路及代码

主要练习使用floyd算法,求多源(最短)路径。个人的解题思路主要是,通过松弛操作记录推测出的胜负关系个数,用原总关系个数-初始化关系数-推测出的胜负关系数即等于无法确定的关系数。除此之外还需要进行适当的剪枝操作,即如果folyd第二层dis[I][k]==0时,即第一个胜负关系无法确定,第三层的for循环可以跳过。

#include<iostream>
#include<cstdio>
using namespace std;
const int N=550;
const int M=550;
int dis[N][N]={0};
int n,m,t;
int cnt;

void init()
{
    cnt=0;
    memset(dis,0,sizeof(dis));
}

void Floyd()
{
    for(int k=1;k<=n;k++)
        for(int i=1;i<=n;i++)
        {
            if(dis[i][k]==0)
                continue;
            for(int j=1;j<=n;j++) {
                if ((dis[i][k] & dis[k][j]) > dis[i][j]) {
                    dis[i][j] = 1;
                    cnt++;
                }
            }
        }
}

int main()
{
    scanf("%d",&t);
    while(t--)
    {
        init();
        scanf("%d",&n);
        scanf("%d",&m);
        for(int i=0;i<m;i++)
        {
            int a,b;
            scanf("%d",&a);
            scanf("%d",&b);
            dis[a][b]=1;
        }
        Floyd();
        //cout<<cnt<<endl;
        cout<<(n*(n-1)/2)-cnt-m<<endl;
    }
    return 0;
}

总结:本道题主要注意folyd的三次循环的嵌套顺序和可行性剪枝。

二、B题 TT的旅行日记

N个站点(包含一个起点和一个终点),其中有M条经济线,K条商业线。每条线路都有起点终点和时间消耗值,要求从起点到终点,输出乘坐任意条经济线和最多一条商业线的最短时间消耗的路径。

1.Sample input and output

input

输入包含多组数据。每组数据第一行为 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 行是商业线路段的描述,格式同经济线。
所有路段都是双向的,但有可能必须使用商业车票才能到达机场。保证最优解唯一。

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

output

对于每组数据,输出3行。第一行按访问顺序给出 TT 经过的各个车站(包括起点和终点),第二行是 TT 换乘商业线的车站编号(如果没有使用商业线车票,输出"Ticket Not Used",不含引号),第三行是 TT 前往喵星机场花费的总时间。
本题不忽略多余的空格和制表符,且每一组答案间要输出一个换行

扫描二维码关注公众号,回复: 10762663 查看本文章
1 2 4
2
5

2.解题思路及代码

整体思路是使用两边dij算法,在经济线中分别求出从起点和终点的单源最短路径。然后遍历每一条商业线,松弛操作起点到商业线起点+商业线+商业线终点到终点。
首先对图的存储使用前向星的方式,Edge[0][N]和Edge[1][N]分别存储从起点的和从终点的。使用两次dij,分别把最短距离存在dis[2][N]里。
在输出路径是时,主要利用了prehead数组,prehead[0][N]主要记录了从起点开始dij时,每个节点的前向节点,prehead[1][N]主要记录了从终点开始的前向(从整体路径上看是后向节点),然后用一个队列来不断访问从起点到商务线起点的节点并存储,用栈来访问从商务线终点到路径终点,然后输出队列,输出商务线,输出栈,即可得到路径输出。

#include<iostream>
#include<cstdio>
#include<queue>
#include<stack>
#include<algorithm>
#include<cstring>
#define inf 1e8;
const int M=1e6;
const int N=1e3+100;
using namespace std;
int Station_num,Road_num,SRoad_num;
//存储从起点开始遍历和从终点开始遍历的前向点,用于输出路径
int prehead[2][N];
struct edge
{
public:
    int u;
    int v;
    int w;
    int nxt;
};
//第一层存储经济线,第二层存储商务线
edge Edges[2][M];
int head[2][N],tot[2];
priority_queue<pair<int,pair<int,int>>> q;
//l区分添加边的层数={0,1}
void addEdge(int u,int v,int w,int l)
{
    Edges[l][tot[l]].u=u;
    Edges[l][tot[l]].v=v;
    Edges[l][tot[l]].w=w;
    Edges[l][tot[l]].nxt=head[l][u];
    head[l][u]=tot[l];
    tot[l]++;
}
//dis两层分别存储从起点std和从终点end的dij结果
int vis[N],dis[2][N];
//dij 数组
void dijkstra(int s,int l)
{
    while(q.size()) q.pop();
    for(int i=1;i<=Station_num;i++)
    {
        vis[i]=0;
        dis[l][i]=inf;
    }
    dis[l][s]=0;
    q.push(make_pair(0,make_pair(s,-1)));
    while(q.size())
    {
        //找到堆顶最小元素
        int x=q.top().second.first;
        int num=q.top().second.second;
        q.pop();
        if(vis[x]) continue;
        prehead[l][x]=num;
        vis[x]=1;
        //松弛操作
        for(int i=head[0][x];i!=-1;i=Edges[0][i].nxt)
        {
            int y=Edges[0][i].v,w=Edges[0][i].w;
            if(dis[l][y]>dis[l][x]+w)
            {
                dis[l][y]=dis[l][x]+w;
                q.push(make_pair(-dis[l][y],make_pair(y,Edges[0][i].u)));
            }
        }
    }
}
//用于存储商务线的两端点到起点、终点的路径
stack<int> path1;
queue<int> path2;
void init()
{
    tot[0]=0;
    tot[1]=0;
    memset(head,-1,sizeof(head));
    memset(prehead,-1,sizeof(prehead));
    while(!path1.empty())
        path1.pop();
    while(!path2.empty())
        path2.pop();
}
int main()
{
    int yat=0;
    while(cin>>Station_num)
    {
        init();
        int st,end;
        scanf("%d",&st);
        scanf("%d",&end);
        scanf("%d",&Road_num);
        for(int i=0;i<Road_num;i++)
        {
            int x,y,z;
            scanf("%d",&x);
            scanf("%d",&y);
            scanf("%d",&z);
            addEdge(x,y,z,0);
            addEdge(y,x,z,0);
        }
        dijkstra(st,0);
        //从st开始到达所有点的距离存储在dis[0]中
        dijkstra(end,1);
        //从end开始到达所有点的距离存储在dis[1]中
        scanf("%d",&SRoad_num);
        for(int i=0;i<SRoad_num;i++)
        {
            int x,y,z;
            scanf("%d",&x);
            scanf("%d",&y);
            scanf("%d",&z);
            addEdge(x,y,z,1);
            addEdge(y,x,z,1);
        }
        int ans=dis[0][end];
        int p1=-1,p2=-1;
        for(int i=0;i<tot[1];i++)
        {
            int x=Edges[1][i].u,y=Edges[1][i].v;
            int tmp=dis[0][x]+dis[1][y]+Edges[1][i].w;
            if(tmp<ans)
            {
                ans=tmp;
                p1=x;
                p2=y;
            }
        }
        if(yat!=0)
            cout<<endl;
        if(p1==-1&&p2==-1)  // 不用经过商务线
        {
            int lim=end;
            while(lim!=-1)
            {
                path1.push(lim);
                lim=prehead[0][lim];
            }
            while(1)
            {
                if(path1.empty())
                    break;
                cout<<path1.top();
                path1.pop();
                if(!path1.empty())
                    cout<<' ';
            }
            cout<<endl;
            cout<<"Ticket Not Used"<<endl;
        }
        else{
            int lim=p1;
            while(lim!=-1)
            {
                path1.push(lim);
                lim=prehead[0][lim];
            }
            while(!path1.empty())
            {
                cout<<path1.top()<<' ';
                path1.pop();
            }
            lim=p2;
            while(lim!=end)
            {
                path2.push(lim);
                lim=prehead[1][lim];
            }
            path2.push(end);
            while(path2.front()!=end)
            {
                cout<<path2.front()<<' ';
                path2.pop();
            }
            cout<<end<<endl;
            cout<<p1<<endl;
        }
        cout<<ans<<endl;
        yat=1;
    }
    return 0;
}

三、C题 TT的美梦 (SPFA)

现有N个城市,每个城市有一个繁荣度。现有M条有向道路,每条道路的税费等于 (目的地繁荣程度 - 出发地繁荣程度)^ 3

先需要多次查询从1号城市到任意城市所需税费情况。

1.Sample input and output

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 号点的最少税费。

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

output

每个询问输出一行,如果不可达或税费小于 3 则输出 ‘?’。

Case 1:
3
4
Case 2:
?
?

2.解题思路及代码

主要利用SPFA进行负环的判断。如果某个点被松弛次数超过n-1次,则该点必定出现在负环上,然后对该点使用dfs,将该点能到达的所有点全部打上负环标记,在SPFA操作的入队列操作中,如果入队元素被打上了负环标记则可以直接continue。

#include<iostream>
#include<queue>
#include<cstring>
const int inf=2e8;
const int N=1e6;
const int M =205;
using namespace std;

int head[M],tot;
int a[M];
int n,m,Q;

int tar[M];

int re(int n1,int n2)
{
    int ans=(n2-n1);
    return (ans*ans*ans);
}

struct edge
{
public:
    int u;
    int v;
    int w;
    int nxt;
}Edges[N];

void addEdge(int u,int v,int w)
{
    Edges[tot].u=u;
    Edges[tot].v=v;
    Edges[tot].w=w;
    Edges[tot].nxt=head[u];
    head[u]=tot;
    tot++;
}

int Vis[M]; //用于DFS

void init()
{
    tot=0;
    memset(head,-1,sizeof(head));
    memset(a,0,sizeof(a));
}

queue<int> q;

int vis[M],dis[M],cnt[M];   //vis用于标记元素是否在队列中

//重制DFS访问标记

void dfs(int u)
{
    tar[u]=1;   //打上负环标记
    //Vis[u]=1;   //打上访问标记
    for(int i=head[u];i!=-1;i=Edges[i].nxt)
    {
        if(tar[Edges[i].v]==0)
            dfs(Edges[i].v);
    }
}

void SPFA(int s)
{
    while(!q.empty()) q.pop();
    for(int i=1;i<=M;i++)
    {
        vis[i]=0;
        tar[i]=0;
        cnt[i]=0;
        dis[i]=inf;
    }
    dis[s]=0;
    vis[s]=1;
    q.push(s);
    while(!q.empty())
    {
        int x=q.front(); q.pop();
        vis[x]=0;
        //如果在负环
        if(tar[x]==1)
            continue;
        for(int i=head[x];i!=-1;i=Edges[i].nxt)
        {
            int y=Edges[i].v;
            //如果在负环
            if(tar[y]==1)
                continue;
                //松弛操作
            if(dis[y]>dis[x]+Edges[i].w)
            {
                dis[y]=dis[x]+Edges[i].w;
                cnt[y]=cnt[x]+1;
                if(cnt[y]>=n)
                {
                    //memset(Vis,0, sizeof(Vis));
                    dfs(y);
                    //continue;
                }
                else if(vis[y]!=1 && tar[y]!=1)
                    vis[y]=1,q.push(y);
            }
        }
    }
}

int main()
{
    int T;
    cin>>T;
    for(int k=0;k<T;k++)
    {
        init();
        cin>>n;
        for(int i=1;i<=n;i++)
            cin>>a[i];
        cin>>m;
        for(int i=0;i<m;i++)
        {
            int n1,n2;
            cin>>n1;
            cin>>n2;
            addEdge(n1,n2,re(a[n1],a[n2]));
        }
        SPFA(1);
        cout<<"Case "<<k+1<<':'<<endl;
        cin>>Q;
        for(int i=0;i<Q;i++)
        {
            int P;
            cin>>P;
            if(dis[P]<3 || tar[P]==1|| dis[P]==inf)
                cout<<'?'<<endl;
            else
                cout<<dis[P]<<endl;
        }
    }
    return 0;
}
发布了8 篇原创文章 · 获赞 2 · 访问量 252

猜你喜欢

转载自blog.csdn.net/lawrenceY/article/details/105485299
今日推荐