CSP认证:行车路线

问题描述

在这里插入图片描述
在这里插入图片描述

大致思路: 最短路+拆点

  1. 显然,本题是一道与图论当中最短路有关的问题,因此考虑使用SPFA / dijkstra等算法。观察本题数据规模和约定,部分测试数据不存在小道,就转化为了经典的最短路模板题,直接背模板就好了。对于还有一部分测试数据“所有小道不相交”,则分开讨论大小道:表示若当前边为大道,边权=当前边疲惫度;若当前边为小道,则边权=当前边疲惫度的平方,从而又转化为了最短路模板题。这样就可以拿到80分~
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
const int N = 510,M=200010;//注意,无向边看作是两条相反方向的有向边,因此边数范围需要*2
int h[N],e[M],ne[M],idx;
long long w[M];//注意c的范围是1e5,若c是小边,经过平方为1e10,超过int范围,因此需要long long
int n,m;
long long dist[N];
queue<int> q;
bool st[N];
void add(int a,int b,long long c){
    e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}

int SPFA(){
    memset(dist,0x3f,sizeof dist);
    dist[1]=0;
    q.push(1);
    st[1]=true;
    while(q.size()){
        int u=q.front();
        q.pop();
        st[u]=false;
        for(int i=h[u];i!=-1;i=ne[i]){
            int j=e[i];
            if(dist[j]>dist[u]+w[i]){
                dist[j]=dist[u]+w[i];
                if(!st[j]){
                    q.push(j);
                    st[j]=true;
                }
            }
        }
    }
    return dist[n];
}

int main(){
    memset(h,-1,sizeof h);
    cin>>n>>m;
    int tag,a,b;
    long long c;
    while(m--){
        cin>>tag>>a>>b>>c;
        if(tag==1){
            add(a,b,c*c);
            add(b,a,c*c);
        }
        else{
            add(a,b,c);
            add(b,a,c);
        }
    }
    cout<<SPFA()<<endl;
}

  1. 还有20分,则需要考虑路径当中可能存在的连续小路的情况。这时候就要引入拆点。个人觉得这与dp的状态表示与状态集合划分非常像。
    点的表示:dist[i][j]——表示从1号点走到第i号点,路径包含最后一段是连续小道的总长度为j的最短路径
    点的划分:根据从1号点走到i号点路径上的最后一条边的类型划分:要么是大道,要么是小道。
    在这里插入图片描述
    (1)大道:很好理解,直接加上最后一条边权即可,注意j=0;
    (2)小道:由于连续的小道是需要先求和再求平方,因此该路径的疲劳度需要先减去原来连续小道所花费的疲劳度,再加上(更新后连续小道总长度之和)的平方,注意更新j=j+w
    这样,同一个点,由于第二维j的不同,被拆成n个点——拆点
    再应用堆优化版的dijkstra算法,即可求得结果。
  2. !!!再仔细阅读题目中的答案限制范围,不超过1e6,表明连续小道的长度之和一定不会超过1000,因此dist[i][j]中j的范围为j<=1000,又缩小了范围。

OK~上代码

#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>

using namespace std;
const int N = 510, M = 200010,INF=0x3f3f3f3f;
int h[N],e[M],ne[M],w[M],tag[M],idx;
int dist[N][1010];//连续小道的长度之和一定不会超过1000
bool st[N][1010];//点的去重标记也跟随拆点
int n,m;

void add(int t,int a,int b,int c){
    e[idx]=b,w[idx]=c,tag[idx]=t,ne[idx]=h[a],h[a]=idx++;
}

struct Node{//拆点,由于y的不同,x号点被拆成若干点
    int x,y,d;
    bool operator<(const Node &p)const{
        return d>p.d;
    }
};
//堆优化版dijkstra
void dijkstra(){
    priority_queue<Node> heap;//priority_queue默认大根堆,由于Node内置排序为降序,因此实际意义还是小根堆
    heap.push({1,0,0});
    memset(dist,0x3f,sizeof dist);
    dist[1][0]=0;
    while(heap.size()){
        Node t=heap.top();
        heap.pop();
        if(st[t.x][t.y])continue;
        st[t.x][t.y]=true;
        for(int i=h[t.x];i!=-1;i=ne[i]){
            int k=e[i],weight=w[i];
            if(tag[i]){//小路
                if(t.y+weight<=1000)//连续小道的长度之和一定不会超过1000
                if(dist[k][t.y+weight]>t.d-t.y*t.y+(t.y+weight)*(t.y+weight)){
                    dist[k][t.y+weight]=t.d-t.y*t.y+(t.y+weight)*(t.y+weight);
                    if(dist[k][t.y+weight]<=INF) heap.push({k,t.y+weight,dist[k][t.y+weight]});
                }
            }
            else{//大路
                if(dist[k][0]>t.d+weight){
                    dist[k][0]=t.d+weight;
                    if(dist[k][0]<=INF) heap.push({k,0,dist[k][0]});
                }
            }
        }
    }
}

int main(){
    memset(h,-1,sizeof h);
    cin>>n>>m;
    int t,a,b,c;
    while(m--){
        cin>>t>>a>>b>>c;
        add(t,a,b,c);
        add(t,b,a,c);
    }
    dijkstra();
    int res=INF;
    for(int i=0;i<=1000;i++){
        res=min(res,dist[n][i]);
    }
    cout<<res<<endl;
}

一些小Tips

  1. CSP第3、4、5题心态先稳住,不要着急,仔细阅读数据规模与约定,部分测试数据可能比较简单,先尝试拿分,再努力尝试拿满分;另外,数据规模里也常有答案是否会爆int的提示,注意把类型定义为long long
  2. !!!拆点的思想方法(如上所述),并且拆点时,点的去重标记也要跟随拆点;
  3. SPFA模板注意点:
    st[] :标记的是当前队列中的点,入队st[i]=true;出队st[i]=false;每次入队之前需要判断当前队列中是否已有该点,即st[i]是否为true,st[i]为false才入队;
  4. dijkstra堆优化模板的注意点:
    (1)st[]:借助堆(小根堆),快速找到当前最短的dist(堆顶元素),将对应的点加入已经过的点的集合st[],再用该点去更新其他点的dist:表明入堆的时候直接push就好了,而堆顶元素出堆时需要把st[i][j]标记为true,再利用堆顶元素去更新dist。
    (2)dist[]:记得初始化!
  5. 用邻接表存无向图时:
    (1)h[]: 记得初始化!!
    (2)M(边数范围):无向图记得翻倍!!

参考

Acwing

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值