拓扑,DP noip 模拟赛 [益智游戏] 题解

这篇博客解析了一个益智游戏的解题过程,涉及两个角色在有向图上寻找最短路径的问题。通过Dijkstra算法求解最短路径,并通过拓扑排序找出共同路径,从而计算最大奖励次数。在实现过程中,博主分享了如何处理特殊情况和避免超时的技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目:益智游戏

(!!!本题很卡时间!!!)

题目描述:

小 P 和小 R 在玩一款益智游戏。游戏在一个正权有向图上进行。
小 P 控制的角色要从 A 点走最短路到 B 点,小 R 控制的角色要从 C 点走最短路到 D 点。
一个玩家每回合可以有两种选择,移动到一个相邻节点或者休息一回合。
假如在某一时刻,小 P 和小 R 在相同的节点上,那么可以得到一次特殊奖励,但是在每个节点上最多只能得到一次。
求最多能获得多少次特殊奖励。

输入:

第一行两个整数 n,m 表示有向图的点数和边数。
接下来 m 行每行三个整数 xi,yi,li,表示从 xi到 yi有一条长度为 li的边。
最后一行四个整数 A,B,C,D,描述小 P 的起终点,小 R 的起终点。

输出:

一个整数表示最多能获得多少次特殊奖励。若小 P 不能到达 B 点或者小 R 不能到达 D点则输出-1。

样例输入:

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

样例输出:

2

简析:

首先,要保证两人都是走的最短路,如果不能到达则输出-1。
然后我们从所有的点中筛选出被两条最短路同时包含的点,再在原图的基础上将这些点建一个新图,用拓扑排序跑一遍(找出最多的公共点,即最长路)。
以上是大致思路。
接下来逐个解决几个问题:
1.Q:跑最短路需要注意什么?
A:跑最短路最好用算法:Dijkstra+优先队列优化。
2.Q:如何找出哪些点同 时在两条最短路中?
A:首先用静态链接表建好图(正反都要建),然后跑四遍最短路,从起点A到终点B,从终点B到起点A,从起点C到终点D,从终点D到起点C,同时用四个数组dis记录下每个点到这四个殊点的最短距离。假设存在一条u到v的边(u指向v),如果A到u的距离+v到B的距离+u到v的距离==A到B的距离,那么u,v两点在A到B最短路中,若同理判 断出u,v同时在C到D的最短路上,那么 v的入度+1,(什么是入度?拓扑排序的内容,不会的话去学习一波),并且将u,v加入新图中(加入什么新图?也是拓扑排序要用的)。
3.Q:关DP什么事儿?
A:我也不太清楚,不过似乎好像大约用到了。
4.Q:还有什么要需要注意的吗?
A:这题要开很多东西,先想清楚在动手,不然就像我一样,用生命去改代码。

代码:

#include<stdio.h>
#include<string.h>
#include<queue>
#include<vector>
#include<algorithm>
#include<iostream>
using namespace std;
const int inf=0x3f3f3f3f;
inline int scan(){       //读入优化
    int x=0;
    char c=getchar();
    while(c>'9'||c<'0')
        c=getchar();
    while(c>='0'&&c<='9')
        x=(x<<1)+(x<<3)+c-'0',
        c=getchar();
    return x;
}
struct node{
    int to,next,dis;
};
node edge[201000][2];          //用于双向建图
int pre[51000][2],edge_num1,dis[5][51000],edge_num2;  //双向建图
int m,n,into[51000],dp[51000];   //拓扑排序
inline void add1(const int &x,const int &y,const int &z){
    edge[++edge_num1][0].next=pre[x][0];
    edge[edge_num1][0].to=y;
    edge[edge_num1][0].dis=z;
    pre[x][0]=edge_num1;  //正向建图


    edge[++edge_num2][1].next=pre[y][1];
    edge[edge_num2][1].to=x;
    edge[edge_num2][1].dis=z;
    pre[y][1]=edge_num2;  //反向建图
}

struct node3{
    int num,dis;
};
struct comp{
    bool operator()(node3 x,node3 y){
        return x.dis>y.dis;
    }
};
priority_queue<node3,vector<node3>,comp> q;  //Dijkstra的优先队列优化

inline void Dijk(int l,int x,int d[]){   //不会Dijkstra?去学习!
    node3 a;
    a.num=x;a.dis=0;      // .num记录节点编号, .dis记录到源点的最短距离
    q.push(a);
    d[x]=0;
    while(!q.empty()){    
        int u=q.top().num;
        q.pop();
        for(int i=pre[u][l];i;i=edge[i][l].next){
            int v=edge[i][l].to;
            if(d[u]+edge[i][l].dis<d[v]){
                d[v]=d[u]+edge[i][l].dis;
                node3 a;
                a.num=v;a.dis=d[v];
                q.push(a);
            }
        }
    }
}
int pret[51000],edge_numt;     //建新图
node edget[201000];            //建新图
inline void add2(const int &x,const int &y){
    edget[++edge_numt].next=pret[x];
    edget[edge_numt].to=y;
    pret[x]=edge_numt;
}                 //将同时在两条最短路的点加入新图
int ans=0;
inline void topord(){           //拓扑排序
    queue<int>q;
    for(int i=1;i<=n;i++)
        if(!into[i]){
            q.push(i);
            dp[i]=1;
        }
    while(!q.empty()){
        int x=q.front();
        q.pop();
        ans=max(ans,dp[x]);
        for(int i=pret[x];i;i=edget[i].next){
            int v=edget[i].to;
            dp[v]=max(dp[v],dp[x]+1);
            into[v]--;
            if(!into[v])
                q.push(v);
        }
    }
}
int main(){
    n=scan();m=scan();
    for(int i=1;i<=m;i++){
        int x=scan(),y=scan(),z=scan();
        add1(x,y,z);
    }
    memset(dis,inf,sizeof(dis));
    int a=scan(),b=scan(),c=scan(),d=scan();
    Dijk(0,a,dis[1]); 
    Dijk(1,b,dis[2]); 
    Dijk(0,c,dis[3]); 
    Dijk(1,d,dis[4]);    //将四个点分别作为源点求最短路,并将距离储存在各自的数组中
    if(dis[1][b]>=inf||dis[3][d]>=inf){
        printf("-1");   //无法到达
        return 0;
    }
    for(int x=1;x<=n;x++){
        for(int i=pre[x][0];i;i=edge[i][0].next){
            int v=edge[i][0].to;
            if(dis[1][x]>=inf||dis[2][v]>=inf||dis[3][x]>=inf||dis[4][v]>=inf)
                continue;
            if(dis[1][x]+edge[i][0].dis+dis[2][v]==dis[1][b]&&dis[3][x]+edge[i][0].dis+dis[4][v]==dis[3][d]){   //判断是否同时在两条最短路中(别搞错正向和反向)
                add2(x,v);
                into[v]++;
            }
        }
    }
    topord();
    printf("%d",ans);
}

这篇可能会超时,,,

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值