[Floyd+剪枝/有条件的SPFA/负环与SPFA]Exercise Week7 A+B+C

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]==INFminu[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;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值