题目描述
风景迷人的小城 \(Y\) 市,拥有 $n $个美丽的景点。
由于慕名而来的游客越来越多,\(Y\) 市特 意安排了一辆观光公交车,为游客提供更便捷的交通服务。
观光公交车在第 \(0\) 分钟出现在 \(1\) 号景点,随后依次前往 $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\)。
那么$ ZZ $该如何安排使用加速器,才能使所有乘客的旅行时间总和最小?
Input
第 \(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\) 位乘客来到出发景点的时刻,出发的景点编号和到达的景点编号.
对于 \(100\%\)的数据,\(1 <=n <=1,000,1 <=m <=10,000,0 <=k <=100,000,0 <=D_i <=100,0 <=T_i <=100,000\)
Output
共一行,包含一个整数,表示最小的总旅行时间。
Sample Input
3 3 2 1 4 0 1 3 1 1 2 5 2 3
Sample Output
10
一道贪心鬼题,\(O(n*k)\)可以卡过。。。
首先,我们要先打出没有加速器的代码。
定义:\(A[i]\)为到\(i\)点的最小时间,\(L[i]\)为起始点为\(i\)的最迟的出发时间,\(D[i]\)为\(i\)到\(i+1\)的路径长度,\(B[i]\)为第\(i\)个人的终点,\(T[i]\)为第\(i\)个人的出发时间。
很显然\(A[i]=max(A[i-1],L[i-1])+D[i-1]\)。
有\(Ans=\sum_{i=1}^{m}A[B[i]]-T[i]\)。
转换一下,\(Ans=-\sum_{i=1}^{m}T[i]+\sum_{i=1}^{m}A[B[i]]\)。
设\(t\)为\(-\sum_{i=1}^{m}T[i]\),则\(Ans=t+\sum_{i=1}^{m}A[B[i]] \cdots (1)\)。
令\(cnt[i]\)为到\(i\)点的数量。
转换\((1)\)式得:\(Ans=t+\sum_{i=1}^{n}cnt[i]*A[i] \cdots(2)\)。
仔细观察\((2)\)式,我们发现,其实如果减掉了\(D[i]\)能更新的\(A[i]\)一定是连续的一段区间。
那么,我们能让答案减小的值也就是区间的长度。
注意!!!不是后缀,因为一个点的\(A[i]\)被更新到了\(L[i]\)以后,就不用对其进行更新。
因为那样一定不会更优。
因此,我们只用每次找出能更新区间最长的一个\(D[i]\),将其减一。
然后,在\(O(n)\)更新所有的\(A[i]\)。
若找不到可以更新的点就直接\(break\)即可。
代码如下
#include <bits/stdc++.h> using namespace std; #define LL long long #define reg register #define Raed Read #define debug(x) cerr<<#x<<" = "<<x<<endl; #define rep(a,b,c) for(reg int a=(b),a##_end_=(c); a<=a##_end_; ++a) #define ret(a,b,c) for(reg int a=(b),a##_end_=(c); a<a##_end_; ++a) #define drep(a,b,c) for(reg int a=(b),a##_end_=(c); a>=a##_end_; --a) inline int Read() { int res = 0, f = 1; char c; while (c = getchar(), c < 48 || c > 57)if (c == '-')f = 0; do res = (res << 3) + (res << 1) + (c ^ 48); while (c = getchar(), c >= 48 && c <= 57); return f ? res : -res; } template<class T>inline bool Min(T &a, T const&b) { return a > b ? a = b, 1 : 0; } template<class T>inline bool Max(T &a, T const&b) { return a < b ? a = b, 1 : 0; } const int N=2e5+5,M=2e5+5; const int dx[4]= {1,-1,0,0},dy[4]= {0,0,1,-1}; bool MOP1; int n,m,k; struct T310 { int A[N],a[N],b[N],D[N],t[N],L[N]; inline void solve(void) { ret(i,1,n)D[i]=Read(); rep(i,1,m)t[i]=Raed(),a[i]=Raed(),b[i]=Raed(),Max(L[a[i]],t[i]); rep(i,1,n)A[i]=max(A[i-1],L[i-1])+D[i-1]; int Ans=0; rep(i,1,m)Ans+=A[b[i]]-t[i]; printf("%d\n",Ans); } } P10; struct T340 { int A[N],a[N],b[N],D[N],t[N],L[N],O[N]; inline void solve(void) { ret(i,1,n)D[i]=Read(); rep(i,1,m) { t[i]=Raed(),a[i]=Raed(),b[i]=Raed(); Max(L[a[i]],t[i]),O[b[i]]++; } rep(i,1,n)A[i]=max(A[i-1],L[i-1])+D[i-1]; while(k--) { int Sum=0,maxx=0,maxi=0; drep(i,n,2) { if(A[i]<=L[i])Sum=0; Sum+=O[i]; if(D[i-1]>0&&Sum>maxx)maxx=Sum,maxi=i-1; } if(!maxi)break; D[maxi]--; rep(i,1,n)A[i]=max(A[i-1],L[i-1])+D[i-1]; } rep(i,1,n)A[i]=max(A[i-1],L[i-1])+D[i-1]; int Ans=0; rep(i,1,m)Ans+=A[b[i]]-t[i]; printf("%d\n",Ans); } } P60; bool MOP2; inline void _main(void) { n=Read(),m=Read(),k=Raed(); if(!k)P10.solve(); else P60.solve(); } signed main() { #define offline1 #ifdef offline freopen("bus.in", "r", stdin); freopen("bus.out", "w", stdout); _main(); fclose(stdin); fclose(stdout); #else _main(); #endif return 0; }
其实本题算法非常多。
我统计了以下几种做法:
1:陈立杰的\(O(n+m*log_n)\)的贪心做法。。
https://wenku.baidu.com/view/9c2b561d227916888486d7f2.html?from=search
2:费用流做法(第一篇题解)
https://www.luogu.org/problemnew/solution/P1315
3:\(O(n*k)\)的贪心