题意
有 n n n 座火车站(原题是地铁,不过不重要)在一条轨道上依次排列,编号为 1 到 n n n。每个车站在 0 : 00 0:00 0:00 时(初始状态)有 a i a_i ai 个人,每小时增加 b i b_i bi 个人,容量为 c i c_i ci 人。你还有足够多的火车,每辆车的大小为 k k k。
每个小时的流程大致如下:
- i : 00 i:00 i:00:第 i i i 小时开始;
-
i
:
30
i:30
i:30:你可以派出任意多辆火车,它们会从 1 到
n
n
n 经过每一个站,并在每一个站装尽可能多的人(要么把站台清空、要么把火车装满);这些火车会在一瞬间经过完这
n
n
n 个站台并
开往非洲开走; - i : 59 i:59 i:59:每个站台增加 b i b_i bi 个人;
- 紧接着判定每个站台目前的人数是否超出限制 c i c_i ci,如果超出你就输了;
- 进入下一个小时。
你希望在 t : 00 t:00 t:00 之前没有站台人数超过限制。求出你至少需要派出多少火车。
n ≤ 300 n\leq 300 n≤300。(原题为 200)
题解
(大部分译自官方题解,部分记号或边界处理会有出入)
一个纯粹的 DP。
首先在末尾添加一个 a = inf , b = 0 , c = inf a=\inf,b=0,c=\inf a=inf,b=0,c=inf 的火车站(顺便把 n n n 给 + 1 +1 +1)。它不影响答案,但能保证所有火车都被装满。
记 s a [ i ] , s b [ i ] , s c [ i ] sa[i],sb[i],sc[i] sa[i],sb[i],sc[i] 分别为 a , b , c a,b,c a,b,c 的前缀和。
记 f [ i , j , z = 0 / 1 ] f[i,j,z=0/1] f[i,j,z=0/1] 表示在只考虑前 i i i 个站台(其初始有 z ⋅ a i z\cdot a_i z⋅ai 个人,即: z = 0 z=0 z=0 表示曾经有火车清空过 1 到 i i i)、火车满载的情况下,坚持 j j j 小时需要的火车数。这个数可能为 inf \inf inf,代表不可能做到。
记 g [ i , j , z = 0 / 1 ] g[i,j,z=0/1] g[i,j,z=0/1] 表示在只考虑前 i i i 个站台(其初始有 z ⋅ a i z\cdot a_i z⋅ai 个人,即: z = 0 z=0 z=0 表示曾经有火车清空过 1 到 i i i)、火车满载的情况下,坚持 j j j 小时、且在 s : 31 s:31 s:31 时 1 到 i − 1 i-1 i−1 号车站全都没有人需要的火车数。这个数可能为 inf \inf inf,代表不可能做到。
答案为 f [ n , t , 1 ] f[n,t,1] f[n,t,1]。
下面计算 f f f 和 g g g:
考虑前 j − 1 j-1 j−1 小时中最近一次有火车接到 i i i 上的人的时刻 r : 30 r:30 r:30。
- 若之前没有过火车接到
i
i
i 上的人:
- 我们只需要保证 f [ i − 1 , j , z ] ≠ inf f[i-1,j,z]\neq \inf f[i−1,j,z]=inf,且第 i i i 个火车站不会炸掉,此时有以下转移:
- f [ i , j , z ] ← f [ i − 1 , j , z ] g [ i , j , z ] ← v a l = ⌈ z ⋅ s a [ i − 1 ] + s ⋅ s b [ i − 1 ] k ⌉ f[i,j,z]\gets f[i-1,j,z]\\g[i,j,z]\gets val=\left\lceil{z\cdot sa[i-1]+s\cdot sb[i-1]\over k}\right\rceil f[i,j,z]←f[i−1,j,z]g[i,j,z]←val=⌈kz⋅sa[i−1]+s⋅sb[i−1]⌉
- 第二个转移的前提是 v a l ⋅ k ≤ z ⋅ s a [ i ] + s ⋅ s b [ i ] val\cdot k\leq z\cdot sa[i]+s\cdot sb[i] val⋅k≤z⋅sa[i]+s⋅sb[i],即第 i + 1 i+1 i+1 个火车站不会有人上车;
- 若这个时刻为
r
:
30
r:30
r:30:
- r : 30 r:30 r:30 到 j : 30 j:30 j:30 分为四个阶段:
- 首先,我们要坚持到 r : 30 r:30 r:30,并且在 r : 31 r:31 r:31 时前 i − 1 i-1 i−1 个火车站被清空;
- 然后为了保证第 i i i 个火车站不会在 r : 31 r:31 r:31 到 j : 30 j:30 j:30 炸掉,我们可能要在 r : 30 r:30 r:30 额外派出几辆火车;
- 接下来,我们还要坚持 j − r j-r j−r 小时,并且不带走第 i i i 个火车站的人;
- 对于计算 g [ i , j , z ] g[i,j,z] g[i,j,z] 的情况,最后我们要额外派出一些火车,清空前 i − 1 i-1 i−1 个车站。
- 阶段一需要派出 g [ i , r , z ] g[i,r,z] g[i,r,z] 辆火车;如果 g [ i , r , z ] = inf g[i,r,z]=\inf g[i,r,z]=inf 转移无法实现;
- 阶段二中,第 i i i 个车站还剩下 r e m = z ⋅ s a [ a ] + r ⋅ s b [ a ] rem=z\cdot sa[a]+r\cdot sb[a] rem=z⋅sa[a]+r⋅sb[a] 个人,为了让它不炸掉,需要派出 x = ⌈ max ( 0 , r e m + ( s − r ) b [ i ] − c [ i ] ) k ⌉ x=\left\lceil\dfrac{\max(0,rem+(s-r)b[i]-c[i])}{k}\right\rceil x=⌈kmax(0,rem+(s−r)b[i]−c[i])⌉ 辆车;如果 k x > r e m kx>rem kx>rem,即第 i + 1 i+1 i+1 个火车站有人上车,那么转移无法实现;
- 阶段三中,我们要让前 i − 1 i-1 i−1 个火车站坚持 r − j r-j r−j 小时,需要派出 f [ i − 1 , j − r , 0 ] f[i-1,j-r,0] f[i−1,j−r,0] 辆火车;如果 f [ i − 1 , j − r , 0 ] = inf f[i-1,j-r,0]=\inf f[i−1,j−r,0]=inf,那么转移无法实现;
- 对于计算 g g g 的情况,阶段三和四中,我们要清空前 i − 1 i-1 i−1 个车站,需要派出 v a l = ⌈ ( j − r ) s b [ i − 1 ] k ⌉ val=\left\lceil\dfrac{(j-r)sb[i-1]}{k}\right\rceil val=⌈k(j−r)sb[i−1]⌉ 辆车;如果 k ⋅ v a l > ( j − r ) s b [ i ] + ( r e m − k ⋅ x ) k\cdot val>(j-r)sb[i]+(rem-k\cdot x) k⋅val>(j−r)sb[i]+(rem−k⋅x),即第 i + 1 i+1 i+1 个火车站有人上车,那么转移无法实现;
- 综上,可以得到转移:
- f [ i , j , z ] ← g [ i , r , z ] + x + f [ i − 1 , j − r , 0 ] g [ i , j , z ] ← g [ i , r , z ] + x + v a l f[i,j,z]\gets g[i,r,z]+x+f[i-1,j-r,0]\\g[i,j,z]\gets g[i,r,z]+x+val f[i,j,z]←g[i,r,z]+x+f[i−1,j−r,0]g[i,j,z]←g[i,r,z]+x+val
- 注意转移的前提。
没有任何奇怪的优化和技巧,时间复杂度 O ( n t 2 ) O(nt^2) O(nt2)。
代码:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
int getint(){
int ans=0,f=1;
char c=getchar();
while(c<'0'||c>'9'){
if(c=='-')f=-1;
c=getchar();
}
while(c>='0'&&c<='9'){
ans=ans*10+c-'0';
c=getchar();
}
return ans*f;
}
const int N=310;
const ll inf=0x3f3f3f3f3f3f3f3fll;
ll a[N],b[N],c[N];
ll sa[N],sb[N],sc[N];
ll f[N][N][2],g[N][N][2];
inline void Min(ll &a,ll b){
(b<a?a=b:0);
}
int main(){
int n=getint(),t=getint(),k=getint();
for(int i=1;i<=n;i++){
a[i]=getint();b[i]=getint();c[i]=getint();
sa[i]=sa[i-1]+a[i];
sb[i]=sb[i-1]+b[i];
sc[i]=sc[i-1]+c[i];
}
n++;a[n]=inf,b[n]=0,c[n]=inf;
sa[n]=sa[n-1]+a[n];
sb[n]=sb[n-1]+b[n];
sc[n]=sc[n-1]+c[n];
memset(f,0x3f,sizeof(f));
memset(g,0x3f,sizeof(g));
memset(f[0],0,sizeof(f[0]));
memset(g[0],0,sizeof(g[0]));
for(int i=1;i<=n;i++){
for(int j=0;j<=t;j++){
for(int z=0;z<=1;z++){
if(f[i-1][j][z]<inf&&a[i]*z+b[i]*j<=c[i]){
Min(f[i][j][z],f[i-1][j][z]);
ll val=(z*sa[i-1]+j*sb[i]+k-1)/k;
if(val*k<=z*sa[i]+j*sb[i]){
Min(g[i][j][z],val);
}
}
for(int r=0;r<j;r++){
if(g[i][r][z]>=inf)continue;
ll rem=z*sa[i]+r*sb[i]-k*g[i][r][z];
ll x=(max(0ll,rem+(j-r)*b[i]-c[i])+k-1)/k;
if(x*k>rem)continue;
if(f[i-1][j-r][0]>=inf)continue;
Min(f[i][j][z],g[i][r][z]+x+f[i-1][j-r][0]);
ll val=((j-r)*sb[i-1]+k-1)/k;
if(val*k>(j-r)*sb[i]+rem-x*k)continue;
Min(g[i][j][z],g[i][r][z]+x+val);
}
// cerr<<"f "<<i<<" "<<j<<" "<<z<<" "<<f[i][j][z]<<endl;
// cerr<<"g "<<i<<" "<<j<<" "<<z<<" "<<g[i][j][z]<<endl;
}
}
}
cout<<f[n][t][1];
return 0;
}