题解 CF1007E Mini Metro(某 SCOI 模拟赛 T3)【DP】

44 篇文章 0 订阅
2 篇文章 0 订阅

题意

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 n300。(原题为 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 zai 个人,即: 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 zai 个人,即: z = 0 z=0 z=0 表示曾经有火车清空过 1 到 i i i)、火车满载的情况下,坚持 j j j 小时、且在 s : 31 s:31 s:31 时 1 到 i − 1 i-1 i1 号车站全都没有人需要的火车数。这个数可能为 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 j1 小时中最近一次有火车接到 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[i1,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[i1,j,z]g[i,j,z]val=kzsa[i1]+ssb[i1]
    • 第二个转移的前提是 v a l ⋅ k ≤ z ⋅ s a [ i ] + s ⋅ s b [ i ] val\cdot k\leq z\cdot sa[i]+s\cdot sb[i] valkzsa[i]+ssb[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 分为四个阶段:
    1. 首先,我们要坚持到 r : 30 r:30 r:30,并且在 r : 31 r:31 r:31 时前 i − 1 i-1 i1 个火车站被清空;
    2. 然后为了保证第 i i i 个火车站不会在 r : 31 r:31 r:31 j : 30 j:30 j:30 炸掉,我们可能要在 r : 30 r:30 r:30 额外派出几辆火车;
    3. 接下来,我们还要坚持 j − r j-r jr 小时,并且不带走第 i i i 个火车站的人;
    4. 对于计算 g [ i , j , z ] g[i,j,z] g[i,j,z] 的情况,最后我们要额外派出一些火车,清空前 i − 1 i-1 i1 个车站。
    • 阶段一需要派出 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=zsa[a]+rsb[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+(sr)b[i]c[i]) 辆车;如果 k x > r e m kx>rem kx>rem,即第 i + 1 i+1 i+1 个火车站有人上车,那么转移无法实现
    • 阶段三中,我们要让前 i − 1 i-1 i1 个火车站坚持 r − j r-j rj 小时,需要派出 f [ i − 1 , j − r , 0 ] f[i-1,j-r,0] f[i1,jr,0] 辆火车;如果 f [ i − 1 , j − r , 0 ] = inf ⁡ f[i-1,j-r,0]=\inf f[i1,jr,0]=inf,那么转移无法实现
    • 对于计算 g g g 的情况,阶段三和四中,我们要清空前 i − 1 i-1 i1 个车站,需要派出 v a l = ⌈ ( j − r ) s b [ i − 1 ] k ⌉ val=\left\lceil\dfrac{(j-r)sb[i-1]}{k}\right\rceil val=k(jr)sb[i1] 辆车;如果 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) kval>(jr)sb[i]+(remkx),即第 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[i1,jr,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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值