[BZOJ1774]过路费+[BZOJ2934]业务

两道关于无向图的最短路的问题:

一.[BZOJ1774]过路费:

【问题描述】

  为了发财,Mr_he在他的农场设置了一系列的规章制度,使得任何一只奶牛在农场中的道路行走,都要向他交过路费。
  农场中有 n 片草地(编号为1~n),并且有 m 条双向道路把这些草地连接起来。Mr_he已经在每条双向道路上设置一个过路费wi。可能有多条道路连接相同的两片草地,但是不存在一条道路连接一片草地和这片草地本身。最值得庆幸的是,奶牛从任意一片草地出发,经过一系列的路径,总是可以抵达其他任意一片草地。
  为了最大化自己的收入。Mr_he在每片草地上也设置了一个过路费用ci。并规定,奶牛从一片草地到另一片草地需要交纳的费用,是经过的所有道路的费用之和,加上经过的所有草地(包括起点和终点)的过路费的最大值。
  任劳任怨的牛们希望去调查一下,它们每一次的出行应该选择那条路径才能最节俭。她们要你写一个程序,接受 k 个问题并且输出每个询问对应的最小花费。第 i 个问题包含两个数字 s,t,表示起点和终点草地。

【输入格式】

  第 1 行:三个用空格隔开的整数:n,m,k。
  第 2 到第 n+1 行:第 i+1 行包含一个单独的整数 c(1<=c<=100000),表示第 i 片草地的费用。
  第 n+2 行到第 n+m+1 行:第 j+n+1 行包含 3 个用空格隔开的整数:a,b,w(1<=a,b<=n,1<=w<=100000),表示一条道路(a,b)的费用为 w 。
  第 n+m+2 到第 n+m+k+1 行:第 i+n+m+1 行表示第 i 个问题,包含两个用空格隔开的整数 s 和 t(1<=s,t<=n 且 s!=t),表示一条询问的起点和终点。

【输出格式】

  第 1 到第 k 行:第 i 行包含一个整数,表示从 s 到 t 的最小花费。

【输入样例】

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

【输出样例】

5
8
9

【样例解释】

  包含5个草地的样例图像:
  这里写图片描述
  
  草地1 到草地3 的道路的“边过路费”为 2,草地3 的“点过路费”为 3。所以总的花费为 2+3=5 。

  要从草地1 走到草地4 ,可以从草地1走到草地3 再走到草地5 最后到达草地4 。如果这么走的话,需要的“边过路费”为 2+1+1=4,需要的点过路非为 4(草地5 的点过路费最大),所以总的花费为 4+4=8。

  而从草地2 走到草地3 的最佳路径是从草地2 出发,抵达草地5,最后到达草地3,这么走的话,边过路费为 3+1=4,点过路费为 5,总花费为 4+5=9。

【数据范围】

对于20%的数据,n<=10, m<=20
对于50%的数据,n<=100, m<=5000
对于100%的数据,1<=n<=250,1<=m<=10000,1<=k<=10000。1<=c,w<=100000

看起来和最短路相似,但是要增加一个路径上点权的最大值,有点棘手。
1.针对20%的数据,直接强行dfs枚举s-t的路径即可。
2.针对100%的数据:
因为s-t的路径一定可以分成s-R,R-t两段,所以如果我们制定一个点R为s-t的路径上的最大点权,那么s-t且必须经过点R的费用为dist[R][s]+dist[R][t](R必须是路径上的最大点权)
法一:根据这一点我们可以预处理(Dijkstra/SPFA)出所以的dist[R][i]表示从R出发走到i,且R为路径上的最大点权的距离。询问时枚举中间结点R,ans=min{dist[R][s]+dist[R][t] |1<=R<=n}
时间复杂度O(n*m*log2n+n*k)
法二:dist[s][t]=dist[R][s]+dist[R][t]看起来很像Floyd算法的结构。
但由于需要保证R是路径中点权最大的点,因此我们可以先将点按照点权大小进行排序,然后按照这个顺序循环。
设两个数组,dist(i,j)表示i到j只考虑边时的最短路径,ans(i,j)表示i到j的最短路径,可以得到
dist(i,j)=min{dist(i,R)+dist(R,j)},
ans(i,j)=min{dist(i,R)+dist(R,j)+max(c[i],c[j],c[R])}
用floyd求出即可,对于每组询问,输出ans(S,T)
时间复杂度O(n3+k)

代码一:

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#include<queue>
using namespace std;
const int maxn=255;
const int maxm=10005;
const int inf=1e8;
int n,m,k,c[maxn],u,v,w,vis[maxn],ans,dist[maxn][maxn];
int np,first[maxn];
struct edge
{
    int to,next,w;
}e[2*maxm];
struct data
{
    int i,d;
    friend bool operator <(data a,data b)
    {
        return a.d>b.d; 
    }
}t;
void addedge(int u,int v,int w)
{
    e[++np]=(edge){v,first[u],w};
    first[u]=np;
}
void dfs(int i,int cost,int co)
{
    if(i==v) 
    {
        ans=min(ans,cost+co);
        return;
    }
    vis[i]=1;
    for(int p=first[i];p;p=e[p].next)
    {
        int j=e[p].to;
        if(vis[j]) continue;
        dfs(j,cost+e[p].w,max(co,c[j]));
    }
    vis[i]=0;
}
void solve30()
{
    for(int i=1;i<=k;i++)
    {
        ans=inf;
        memset(vis,0,sizeof(vis));
        scanf("%d%d",&u,&v);
        dfs(u,0,c[u]);
        printf("%d\n",ans);
    }
}
void DIJ(int s,int *dist){
    priority_queue<data>pq;
    pq.push((data){s,0});
    while(!pq.empty())
    {
        t=pq.top();pq.pop();
        if(t.d>dist[t.i]) continue;
        dist[t.i]=t.d;
        for(int p=first[t.i];p;p=e[p].next){
            int j=e[p].to;
            if(c[j]>c[s]) continue;
            if(t.d+e[p].w<dist[j]){
                dist[j]=t.d+e[p].w;
                pq.push((data){j,dist[j]});
            }
        }
    }
} 
void ready(){
    memset(dist,10,sizeof(dist));
    for(int i=1;i<=n;i++){
        memset(vis,0,sizeof(vis));
        DIJ(i,dist[i]);//dist[i][j]表示从i走到j,最大点权为c[i]的最短路径 
    }
}
void solve()
{
    ready();
    for(int i=1;i<=k;i++){
        scanf("%d%d",&u,&v);
        ans=inf;
        for(int j=1;j<=n;j++){
            ans=min(ans,dist[j][u]+dist[j][v]+c[j]);
        }
        printf("%d\n",ans);
    }
}
int main()
{
//  freopen("roadtoll.in","r",stdin);
//  freopen("roadtoll.out","w",stdout);
    scanf("%d%d%d",&n,&m,&k);
    for(int i=1;i<=n;i++) scanf("%d",&c[i]); 
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d%d",&u,&v,&w);
        addedge(u,v,w);
        addedge(v,u,w);
    }
    if(n<=20) solve30();
    solve();
    return 0;
}

代码二:

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#include<queue>
using namespace std;
const int maxn=255;
const int maxm=10005;
const int inf=1e8;
int n,m,K,u,v,w,vis[maxn],dist[maxn][maxn],ans[maxn][maxn],c[maxn];
struct data{
    int w,id;
    friend bool operator <(data a,data b)
    {
        return a.w<b.w;
    }
}cc[maxn];
void floyd(){
    sort(cc+1,cc+1+n);
    for(int k=1;k<=n;k++)
    for(int i=1;i<=n;i++)
    for(int j=1;j<=n;j++){
        dist[i][j]=min(dist[i][cc[k].id]+dist[cc[k].id][j],dist[i][j]);
        ans[i][j]=min(ans[i][j],dist[i][j]+max(cc[k].w,max(c[i],c[j])));
    }
}
void solve()
{
    floyd();
    for(int i=1;i<=K;i++){
        scanf("%d%d",&u,&v);
        printf("%d\n",ans[u][v]);
    }
}
int main()
{
//  freopen("roadtoll.in","r",stdin);
//  freopen("roadtoll.out","w",stdout);
    scanf("%d%d%d",&n,&m,&K);
    memset(ans,0x3f,sizeof(ans));
    memset(dist,0x3f,sizeof(dist));
    for(int i=1;i<=n;i++){
        scanf("%d",&cc[i].w);
        c[i]=cc[i].w;
        cc[i].id=i;
        ans[i][i]=cc[i].w,dist[i][i]=0;
    }
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d%d",&u,&v,&w);
        dist[u][v]=dist[v][u]=min(dist[u][v],w);
    }
    solve();
    return 0;
}

二.业务(加强版)

【问题描述】

  Mr_H谋得一份兼职——货车司机,从此以后他将会开着货车穿行在C国的各大城市之间。

  C国中有n座城市(编号为1~n),并且有m条双向公路,每条公路连接两座不同的城市。货车从任意一座城市出发都可以抵达任意另一座城市。在每条公路上,都有一个收费站,通过的车辆需要交纳一定过路费。可能有多条公路连接相同的两座城市。

  为了增加财政收入,C国还在每座城市也设置了收费站。并且规定,车辆从一座城市到另一座城市的费用是,所经过公路费用和,加上所经过的城市中费用的次大值(这里的次大可以和最大相同,但是城市不同)。

  现在Mr_H告诉你今年k次业务运送货物的起点、终点城市列表,请你帮忙计算,每次业务需要交纳的最低过路费。

【输入格式】

  第一行包含三个用一个空格隔开的整数:n,m,k。其意义如题目描述。
  第2到第 n+1 行:第i+1行包含一个单独的整数 c(1<=c<=100000),表示城市i的费用。
  接下来的m行,每行包含三个整数a,b,w,表示一条公路连接城市a和城市b(1<=a,b<=n),其过路费为w(1<=w<=100000)。
  最后的k行,每行包含两个整数:s,t,表示一次业务的起点和终点(1<=s,t<=n 且 s!=t)。

【输出格式】

  共k行,每行一个整数,表示从城市s到t的最少过路费。

【输入样例】

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

【输出样例】

4
7
8

【数据范围】

对于20%的数据,n<=10, m<=20
对于50%的数据,n<=100, m<=5000
对于100%的数据,1<=n<=250 , 1<=m<=10000 , 1<=k<=10000 ,其中有50%的数据点权没有重复。

和刚才的思路相似,但是有两种情况(R为最大点/次大点)
考虑先预处理出dist[R][i][0]表示从R到i,R是路径上的最大点的最短路径
dist[R][i][1]表示从R到i,R是路径上的次大点的最短路径
那么ans[s][t]=min{min( dist[R][s][0]+dist[R][s][1] , dist[R][s][1]+dist[R][t][0])+c[R] |1<=R<=n}
这里采用Dijkstra计算dist[start][i][x]:
枚举到点j:
1.如果c[j] < c[start]:那么j点不会对当前路径造成影响,该怎么地就怎么地;
2.如果c[j]==c[start]:那么由题意知,start点可以是最大点,也可以是次大点(一定要有次大点,但又要保留原来的状态)
3.如果c[j]>c[start]:那么start一定是次大点,但如果当前已经找到了次大点,就continue;

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#include<queue>
#include<ctime>
using namespace std;
const int maxn=255;
const int maxm=10005;
const int inf=1e8;
int n,m,k,dis[maxn][2],ans[maxn][maxn],c[maxn],u,v,w;
int first[maxn],np=0;
struct edge{
    int to,next,w;
}e[2*maxm];
struct data{
    int i,d,tmp;
    friend bool operator <(data a,data b){
        return a.d>b.d;
    }
}t,tt;
void addedge(int u,int v,int w){
    e[++np]=(edge){v,first[u],w};
    first[u]=np;
}
priority_queue<data>pq;
void DIJ(int s){
    memset(dis,0x3f,sizeof(dis));
    pq.push((data){s,0,0});
    while(!pq.empty()){
        t=pq.top();pq.pop();
        if(t.d>dis[t.i][t.tmp]) continue;
        dis[t.i][t.tmp]=t.d;
        for(int p=first[t.i];p;p=e[p].next){
            int j=e[p].to;
            tt=(data){j,t.d+e[p].w,t.tmp};
            if(j==s) continue;

            if(c[j]>c[s]){//s可以是次大点
                if(t.tmp==1) continue;
                if(tt.d<dis[j][1]){
                    tt.tmp=1;
                    dis[j][1]=tt.d;
                    pq.push(tt);
                }
            }
            else if(c[j]==c[s]){//可以是次大也可以不是 
                if(tt.d<dis[j][tt.tmp]){
                    dis[j][tt.tmp]=tt.d;
                    pq.push(tt);
                }
                if(tt.tmp!=1 && tt.d<dis[j][1]){
                    dis[j][1]=tt.d;
                    pq.push((data){tt.i,tt.d,1});
                }
            }
            else if(tt.d<dis[j][tt.tmp]){//和j无关 
                dis[j][tt.tmp]=tt.d;
                pq.push(tt);
            }
        }
    }
}
void solve(){
    for(int k=1;k<=n;k++){
        DIJ(k);//计算以k为最大点权 / 次大点权的最小费用 
        for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++){
            ans[i][j]=min(ans[i][j],min(dis[i][1]+dis[j][0],dis[i][0]+dis[j][1])+c[k]);
        }
    }
}
int main(){
    scanf("%d%d%d",&n,&m,&k);
    memset(ans,0x3f,sizeof(ans));
    for(int i=1;i<=n;i++){
        scanf("%d",&c[i]);
    }
    for(int i=1;i<=m;i++){
        scanf("%d%d%d",&u,&v,&w);
        addedge(u,v,w);
        addedge(v,u,w);
    }
    solve();
    for(int i=1;i<=k;i++){
        scanf("%d%d",&u,&v);
        printf("%d\n",ans[u][v]);
    }
    return 0;
}

对于什么重边啊之类的就不予理睬好了;

(P.S:次短路的问题都比最短路难理解得多欸。。。)

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: bzoj作为一个计算机竞赛的在线评测系统,不仅可以提供大量的题目供程序员练习和学习,还可以帮助程序员提升算法和编程能力。为了更好地利用bzoj进行题目的学习和刷题,制定一个bzoj做题计划是非常有必要的。 首先,我们需要合理安排时间,每天留出一定的时间来做bzoj的题目。可以根据自己的时间安排,每天挑选适量的题目进行解答。可以先从难度较低的题目开始,逐渐提高难度,这样既能巩固基础知识,又能挑战自己的思维能力。 其次,要有一个计划和目标。可以规划一个每周或每月的题目数量目标,以及每个阶段要学习和掌握的算法知识点。可以根据bzoj的题目分类,如动态规划、图论、贪心算法等,结合自己的实际情况,有针对性地选择题目进行学习。 此外,要充分利用bzoj提供的资源。bzoj网站上有很多高质量的题解和优秀的解题代码,可以参考和学习。还有相关的讨论区,可以与其他程序员交流和讨论,共同进步。 最后,要坚持并保持思考。做题不是单纯为了刷数量,更重要的是学会思考和总结。遇到难题时,要有耐心,多思考,多尝试不同的解法。即使不能一次性解出来,也要学会思考和分析解题过程,以及可能出现的错误和优化。 总之,bzoj做题计划的关键在于合理安排时间、制定目标、利用资源、坚持思考。通过有计划的刷题,可以提高算法和编程能力,并培养解决问题的思维习惯,在计算机竞赛中取得更好的成绩。 ### 回答2: bzoj做题计划是指在bzoj这个在线测评系统上制定一套学习和刷题的计划,并且将计划记录在excel表格中。该计划主要包括以下几个方面的内容。 首先是学习目标的设定。通过分析自己的水平和知识缺口,可以设定一个合理的目标,比如每天解决一定数量的题目或者提高特定的算法掌握程度。 其次是题目选择的策略。在excel表格中可以记录下自己选择的题目编号、题目类型和难度等信息。可以根据题目的类型和难度来安排每天的刷题计划,确保自己可以逐步提高技巧和解题能力。 然后是学习进度的记录和管理。将每天的完成情况记录在excel表格中,可以清晰地看到自己的学习进度和任务完成情况。可以使用图表等功能来对学习进度进行可视化展示,更好地管理自己的学习计划。 同时,可以在excel表格的备注栏中记录下每道题目的解题思路、关键点和需要复习的知识点等信息。这样可以方便自己回顾和总结,巩固所学的知识。 最后,可以将excel表格与其他相关资料进行整合,比如算法教材、题目解析和学习笔记等。这样可以形成一个完整的学习档案,方便自己进行系统的学习和复习。 总之,bzoj做题计划excel的制定和记录可以帮助我们更加有条理和高效地进行学习和刷题。通过合理安排学习目标和题目选择策略,记录学习进度和思路,并整合其他学习资料,我们可以提高自己的解题能力,并在bzoj上取得更好的成绩。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值