A - Environment-Friendly Travel Gym - 102501A (前向星建图+二维迪杰斯特拉算法)

题目链接

题意:
给定起点(xs,ys),
终点(xd,yd),
最大旅程距离B
汽车的每公里代价C0
其余交通方式数量T<=100
其余交通方式代价[C1…CT]<=100
车站数量N<=1000
第i个车站的位置(xi,yi),边数li<=100,每条边(j,mj)表示连向第j个车站,交通方式为mj
可以开车从s到任一车站或目的地,从任一车站开车到目的地,但是车站间不得开车
问在旅程B内的最小代价

简化:无向图,起点s,终点t,边(u,v,a,b),要求s->t的路径边权b<=B条件下,边权a最小
直接用二维dij算法,d[u][j]表示到点u,累计路程b==j情况下代价w的最小值

思路:首先这题给出的数据比较复杂,用链式前向星建图时间效率更优。
每条边存公里数b,代价w。
于是问题简化成,在一个有双重边权的图里面,求出出发点到终点的最短路程(代价w最小),且另一个的权重积累不能超过B
//二维迪杰斯特拉算法问题,把dist数组以及vis数组改成2维 优先队列的节点改成3维即可

注意数据大小maxn要2e5 ,因为边数可能达到2e5

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
#define INF 0x3f3f3f
#define LL long long
const int maxn=2e5+5;
struct 
{
    int x,y;
}p[maxn];//点坐标
struct Node
{
    int u,tw,tb;//顶点信息 ,i到u的最优co2tw,对应的公里数tb
    friend bool operator < (const Node &a,const Node &b)
    {
        if(a.tw!=b.tw) return a.tw>b.tw;
        else return a.tb>b.tb;
    }
};//优先队列节点 w小的优先
struct node
{
    int to;
    int w,b;//w是co2排放量  b是公里数
    int next;
}edge[maxn];//链式前向星建图,优化时间
int head[maxn];//以i为顶点的插入的最后一条边所在edge的下标
int cnt=1;//边的总数,从1开始
vector<pair<int,int> >G[maxn];//第i个点的所有映射
int sx,sy,dx,dy,B,c0,T,n;
int c[maxn];
int dist[1005][105];//二维迪杰斯特拉算法,走到第i个顶点  公里数为j时的最优解
bool vis[1005][105];//vis也要对应变成二维的
void add(int v,int to,int w,int b)//链式前向星插入边
{
    edge[cnt].b=b;
    edge[cnt].to=to;
    edge[cnt].w=w;
    edge[cnt].next=head[v];//edge[i].next 表示和第cnt条边同起点的上一条边所在下标,而最终i是最后插入的边
    head[v]=cnt++;//head[i]表示以i为起点的,最后一条插入的边的下标
}
int calc(int x1,int y1,int x2,int y2)
{
    return ceil(sqrt(1.0*(x1-x2)*(x1-x2)+(y1-y2)*(y1-y2)));//向上取整算两点距离
}
//用链式前向星建图,每个边存co2排放量 公里数
//问题转化成了:在一个有双重边权的图里面,求出出发点到终点的最短路程(co2),且另一个权重不能超过B
//经典的二维迪杰斯特拉算法问题,把dist数组改成2维  优先队列的节点改成3维即可
void build()
{
    for(int i=1;i<=n;i++)//车站之间建双向边
    {
        for(auto j:G[i])
        {
            int dis=calc(p[i].x,p[i].y,p[j.first].x,p[j.first].y);
            add(i,j.first,dis*c[j.second],dis);
            add(j.first,i,dis*c[j.second],dis);
        }
    }
    //起点终点分别设为n+1 n+2号顶点,也得建边
    int dis=calc(sx,sy,dx,dy);
    add(n+1,n+2,dis*c0,dis);//起点到终点的单向边
    //接下来起点和车站  车站和终点建边
    for(int i=1;i<=n;i++)
    {
        int dis1=calc(sx,sy,p[i].x,p[i].y);
        add(n+1,i,dis1*c0,dis1);
        dis1=calc(dx,dy,p[i].x,p[i].y);
        add(i,n+2,dis1*c0,dis1);
    }
}
void dijkstra()
{
    priority_queue<Node> q;
    fill(dist[0],dist[0]+1005*105,INF);
    q.push({n+1,0,0});//起点入队
    while(!q.empty())
    {
        Node temp=q.top();
        q.pop();
        if(vis[temp.u][temp.tb]) continue;
        vis[temp.u][temp.tb]=1;
        for(int i=head[temp.u];i;i=edge[i].next)
        {
            int u=edge[i].to;
            //这个状态没被标记 && 不超过B && 代价w更小
            if(!vis[u][temp.tb+edge[i].b] && temp.tb+edge[i].b<=B &&
                edge[i].w+temp.tw<dist[u][temp.tb+edge[i].b])
            {
                dist[u][temp.tb+edge[i].b]=edge[i].w+temp.tw;
                q.push({u,dist[u][temp.tb+edge[i].b],temp.tb+edge[i].b});
            }
        }
    }
}
int main()
{
    scanf("%d%d%d%d%d%d%d",&sx,&sy,&dx,&dy,&B,&c0,&T);
    for(int i=1;i<=T;i++) scanf("%d",c+i);
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d%d",&p[i].x,&p[i].y);
        int m;
        scanf("%d",&m);
        while(m--)
        {
            int j,cj;
            scanf("%d%d",&j,&cj);
            G[i].push_back({j+1,cj});//每个顶点映射这样一个集合,用于建图
        }
    }
    build();
    dijkstra();
    int ans=INF;
    for(int i=0;i<=B;i++) ans=min(ans,dist[n+2][i]);
    if(ans==INF) ans=-1;
    printf("%d\n",ans);
    system("pause");
    return 0;
}

你可能感兴趣的:(图论,算法,dijkstra)