[NOIP2011 提高组复赛]Day2观光公交题解

题目描述

风景迷人的小城 Y 市,拥有 n 个美丽的景点。由于慕名而来的游客越来越多,Y 市特意安排了一辆观光公交车,为游客提供更便捷的交通服务。观光公交车在第 00 分钟出现在 11 号景点,随后依次前往 2,3,4,⋯,n 号景点。从第 i 号景点开到第 i+1 号景点需要 D_i​ 分钟。任意时刻,公交车只能往前开,或在景点处等待。

设共有 m 个游客,每位游客需要乘车 1 次从一个景点到达另一个景点,第 i 位游客在 T_i​ 分钟来到景点 A_i​,希望乘车前往景点 B_i​(A_i​<B_i​)。为了使所有乘客都能顺利到达目的地,公交车在每站都必须等待需要从该景点出发的所有乘客都上车后才能出发开往下一景点。

假设乘客上下车不需要时间。一个乘客的旅行时间,等于他到达目的地的时刻减去他来到出发地的时刻。因为只有一辆观光车,有时候还要停下来等其他乘客,乘客们纷纷抱怨旅行时间太长了。于是聪明的司机 ZZ 给公交车安装了 k 个氮气加速器,每使用一个加速器,可以使其中一个 D_i​−1。对于同一个 D_i​ 可以重复使用加速器,但是必须保证使用后 D_i​≥0。

那么 Z 该如何安排使用加速器,才能使所有乘客的旅行时间总和最小?

输入格式

第 1 行是 3 个整数n,m,k,每两个整数之间用一个空格隔开。分别表示景点数、乘客数和氮气加速器个数。

第 2 行是 n−1 个整数,每两个整数之间用一个空格隔开,第 i 个数表示从第 i 个景点开往第 i+1 个景点所需要的时间,即 D_i​。

第 3 行至 m+2 行每行 3 个整数 T_i​,A_i​,B_i​,每两个整数之间用一个空格隔开。第 i+2 行表示第 i 位乘客来到出发景点的时刻,出发的景点编号和到达的景点编号。

输出格式

一个整数,表示最小的总旅行时间。

输入输出样例

输入 #1

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

输出 #1

10

说明/提示

【输入输出样例说明】

对 D2​ 使用 2 个加速器,从 2 号景点到 3 号景点时间变为 2 分钟。

公交车在第 1 分钟从 1 号景点出发,第 2 分钟到达 2 号景点,第 5 分钟从 2 号景点出发,第 7 分钟到达 3 号景点。

第 1 个旅客旅行时间 7−0=7 分钟。

第 2 个旅客旅行时间 2−1=1 分钟。

第 3 个旅客旅行时间 7−5=2 分钟。

总时间 7+1+2=10 分钟。

【数据范围】

对于 10% 的数据,k=0。

对于 20% 的数据,k=1。

对于 40% 的数据,2≤n≤50,1≤m≤10^3,0≤k≤20,0≤D_i​≤10,0≤T_i​≤500。

对于 60% 的数据,1≤n≤100,1≤m≤10^3,0≤k≤100,0≤D_i​≤100,0≤Ti​≤10^4。

对于 100% 的数据,1≤n≤10^3,1≤m≤10^4,0≤k≤10^5,0≤D_i​≤100,0≤Ti​≤10^5

题目分析

首先构建模型

不难发现在一个点使用加速器造成的效果是阶段性的

即在一个阶段后区间会被劈开

考虑这个劈开的条件

当前面使用的加速器超过max(tim_i​−Mx_i​,0)时就不能对后面的产生影响了

把使用加速器个数作为流量

每个点都会被分配到至多D_i​的加速器

建立S、′S′、T三个点

连边′S→S′ 容量为K 费用为 0 起到限制总个数的作用

把所有点i分为′i′和′′i′′

连边 i′→i′′ 容量为max(tim_i​−Mx_i​,0) 费用为0 限制前面使用的加速器对后面的影响

连边 S′→i′′ 容量为D_i​ 费用为0 分配加速器

连边 i′′→(i+1)′ 容量为 INF 费用为−down_i+1​ 累计费用以及传递影响

连边 i′→T 容量为INF 费用为0

显然1′和n′′是没有用的 可以在建图时舍去

然后就可以愉快的套模板了

最坏复杂度O(k∗n∗log(n))(Dijkstra)或O(k∗n2)(SPFA)

比贪心慢= =

AC代码

#include<bits/stdc++.h>
using namespace std;
#define N 1010
#define M 10010
#define INF (0x3f3f3f3f)
struct peo{
    int t,l,r;
}a[M];
struct edge{
    int nxt,to,cap,cost;
}e[N<<3];
int head[N<<1],edge_cnt;
void add_edge(int x,int y,int z,int w){
    e[edge_cnt]=(edge){head[x],y,z,w};
    head[x]=edge_cnt++;
    e[edge_cnt]=(edge){head[y],x,0,-w};
    head[y]=edge_cnt++;
}
int D[N],Mx[N],down[N],tim[N],S,T;
struct MinCostMaxFlow{
    int d[N<<1],fa[N<<1],Mn[N<<1];
    bool vis[N<<1];
    queue<int>Q;
    int calc(){
        int i,res=0;
        while (1){
            memset(d,63,sizeof(d));
            d[S]=0;
            Q.push(S);
            Mn[S]=INF;
            while (!Q.empty()){
                int x=Q.front(); Q.pop();
                vis[x]=0;
                for (i=head[x];~i;i=e[i].nxt){
                    int To=e[i].to;
                    if (!e[i].cap || d[To]<=d[x]+e[i].cost) continue;
                    d[To]=d[x]+e[i].cost;
                    fa[To]=i;
                    Mn[To]=min(Mn[x],e[i].cap);
                    if (!vis[To]){
                        vis[To]=1;
                        Q.push(To);
                    }
                }
            }
            if (d[T]==INF) return res;
            res+=Mn[T]*d[T];
            int p=T;
            while (p!=S){
                e[fa[p]].cap-=Mn[T];
                e[fa[p]^1].cap+=Mn[T];
                p=e[fa[p]^1].to;
            }
        }
    }
}MCMF;
int main(){
    memset(head,-1,sizeof(head));
    int n,m,K,i,ans=0;
    scanf("%d%d%d",&n,&m,&K);
    for (i=1;i<n;i++) scanf("%d",&D[i]);
    for (i=1;i<=m;i++){
        scanf("%d%d%d",&a[i].t,&a[i].l,&a[i].r);
        down[a[i].r]++;
        Mx[a[i].l]=max(Mx[a[i].l],a[i].t);
    }
    for (i=1;i<n;i++) tim[i+1]=max(tim[i],Mx[i])+D[i];
    for (i=1;i<=m;i++) ans+=tim[a[i].r]-a[i].t;
    
    S=n*2+1; T=n*2+3;
    int S1=n*2+2;
    add_edge(S,S1,K,0);
    for (i=1;i<n;i++){
        add_edge(i,i+n,max(tim[i]-Mx[i],0),0);
        add_edge(i+n,i+1,INF,-down[i+1]);
        add_edge(S1,i+n,D[i],0);
        add_edge(i+1,T,INF,0);
    }
    printf("%d\n",ans+MCMF.calc());
    return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值