
本文共 3712 字,大约阅读时间需要 12 分钟。
题目
题目背景
“一次一瓶不是很正常吗?”——瓶瓶牛
题目描述
有 n n n 个房间,编号为 1 1 1 到 n n n 。有 n − 1 n-1 n−1 个铁门,第 i i i 个铁门连接房间 i i i 和 i + 1 i+1 i+1 。
铁门在正常情况下是关闭着的(不然你会被小白活活射死),要打开第 i i i 个铁门需要有 a i a_i ai 个「瓶儿」放在房间 i i i 的右侧压力板上,或者 b i b_i bi 个「瓶儿」放在房间 i + 1 i+1 i+1 的左侧压力板上。
当第 i i i 个铁门是开着的时候,你可以在 i i i 和 i + 1 i+1 i+1 房间之间任意移动「瓶儿」。众所周知,一旦「瓶儿」从压力板上离开,铁门会立刻(没有延迟)关上,所以 i i i 号房间至少剩 a i a_i ai 个「瓶儿」,或者 i + 1 i+1 i+1 号房间剩下 b i b_i bi 个「瓶儿」。
在房间 1 1 1 有一个铁门通往出口,要打开这个隧道需要 m m m 个「瓶儿」。如果要保证这个铁门不会被打开,最多可以有多少个「瓶儿」(你可以指定他们初始在哪个房间)呢?
数据范围与提示
n ≤ 1 0 3 n\le 10^3 n≤103 且 max ( m , a i , b i ) ≤ 1 0 4 \max(m,a_i,b_i)\le 10^4 max(m,ai,bi)≤104 。提示:此题并不难,只是需要耐心一点。
思路
注:
由于题面被魔改过,下文用 “人” 代表「瓶儿」。
可能的情况其实很少。罗列一下就知道了。下面用 x 1 , x 2 , … , x n x_1,x_2,\dots,x_n x1,x2,…,xn 表示实际上每个房间的人数,用 f ( v ) f(v) f(v) 表示「假设没有第一个房间,每个房间中的实际人数是 v , x 3 , x 4 , … , x n v,x_3,x_4,\dots,x_n v,x3,x4,…,xn 时,能够走到 2 2 2 号房间(即 v v v 所在的房间)的最大人数」。
- 门根本打不开。 x 1 < a 1 x_1<a_1 x1<a1 且 f ( x 2 ) < b 1 f(x_2)<b_1 f(x2)<b1 。
- 右边的把左边接过去。 f ( x 2 ) ≥ b 1 f(x_2)\ge b_1 f(x2)≥b1 。
- 如果 f ( x 2 + x 1 ) ≥ b 1 + a 1 f(x_2+x_1)\ge b_1+a_1 f(x2+x1)≥b1+a1,可以全部过去,必须 < m <m <m 。
- 否则,只需要 f ( x 2 + x 1 ) < m + b 1 f(x_2+x_1)<m+b_1 f(x2+x1)<m+b1 即可。
- 左边的给右边开门。 x 1 ≥ a 1 x_1\ge a_1 x1≥a1 。
- 如果 f ( x 2 + x 1 − a 1 ) ≥ b 1 f(x_2+x_1-a_1)\ge b_1 f(x2+x1−a1)≥b1,蕴含在
右边把左边接过去
的情况中,不用考虑了。 - 无论如何, f ( x 2 + x 1 − a 1 ) < m − a 1 f(x_2+x_1-a_1)<m-a_1 f(x2+x1−a1)<m−a1 是有必要的。
- 如果 f ( x 2 + x 1 − a 1 ) ≥ b 1 f(x_2+x_1-a_1)\ge b_1 f(x2+x1−a1)≥b1,蕴含在
其中 f ( x 2 + x 1 ) f(x_2+x_1) f(x2+x1) 计算的总人数是没算错的, f ( x 2 + x 1 − a 1 ) f(x_2+x_1-a_1) f(x2+x1−a1) 是少算了 a 1 a_1 a1 个的。
g ′ ( m ) = { min ( m , a 1 ) − 1 + g ( b 1 ) g ( m ) ( b 1 + a 1 < m ) g [ min ( m + b 1 , a 1 + b 1 ) ] g [ min ( m − a 1 , b 1 ) ] + a 1 g'(m)= \begin{cases} \min(m,a_1)-1+g(b_1)\\ g(m)&(b_1+a_1<m)\\ g[\min(m+b_1,a_1+b_1)]\\ g[\min(m-a_1,b_1)]+a_1 \end{cases} g′(m)=⎩⎪⎪⎪⎨⎪⎪⎪⎧min(m,a1)−1+g(b1)g(m)g[min(m+b1,a1+b1)]g[min(m−a1,b1)]+a1(b1+a1<m)
这些值取 max \max max 就好了。为啥可以直接取 max \max max 啊?因为它们都可以还原出一种合法方案。
- min ( a 1 , m ) − 1 + g ( b 1 ) \min(a_1,m)-1+g(b_1) min(a1,m)−1+g(b1) 即第一道门永远打不开。
- g ( m ) g(m) g(m) 即第一个房间没人,第二个房间也没得 m m m 个人。纵使 m ≤ a 1 + b 1 m\le a_1+b_1 m≤a1+b1 也可以转移,因为它显然不会更新答案。
- g [ min ( m + b 1 , a 1 + b 1 ) ] g[\min(m+b_1,a_1+b_1)] g[min(m+b1,a1+b1)] 即第一个房间没人,第二个房间少于 m + b 1 m+b_1 m+b1 也少于 a 1 + b 1 a_1+b_1 a1+b1 。
- g [ min ( m − a 1 , b 1 ) ] + a 1 g[\min(m-a_1,b_1)]+a_1 g[min(m−a1,b1)]+a1 即第一个房间 a 1 a_1 a1 个人,第二个房间的人不足以打开门,而一号房间的人打开门之后,第二个房间的人又不足 m − a 1 m-a_1 m−a1,没法逃生。
显然 m ≥ max ( a i + b i ) m\ge\max(a_i+b_i) m≥max(ai+bi) 无益。复杂度 O ( n ⋅ max ( a i + b i , m ) ) \mathcal O(n\cdot\max(a_i+b_i,m)) O(n⋅max(ai+bi,m)) 。
代码
#include <cstdio> #include <iostream>#include <vector>#include <cstring>using namespace std;inline int readint(){ int a = 0; char c = getchar(), f = 1; for(; c<'0'||c>'9'; c=getchar()) if(c == '-') f = -f; for(; '0'<=c&&c<='9'; c=getchar()) a = (a<<3)+(a<<1)+(c^48); return a*f;}inline void writeint(int x){ if(x > 9) writeint(x/10); putchar((x-x/10*10)^48);}inline void getMax(int &a,const int &b){ (a < b) ? (a = b) : 0;}const int MaxN = 1005;const int MaxM = 20005;int dp[2][MaxM], a[MaxN], b[MaxN];int main(){ int n = readint(), m = readint(); for(int i=1; i<n; ++i){ a[i] = readint(); b[i] = readint(); } for(int j=1; j<MaxM; ++j) dp[n&1][j] = j-1; // less than j for(int i=n-1; i>=1; --i){ int fr = (i&1)^1; for(int j=1; j<MaxM; ++j){ dp[i&1][j] = min(a[i],j)-1+dp[fr][b[i]]; if(a[i] <= j) getMax(dp[i&1][j], dp[fr][min(j-a[i],b[i])]+a[i]); getMax(dp[i&1][j],dp[fr][j]); getMax(dp[i&1][j],dp[fr][min(j,a[i])+b[i]]); } } printf("%d\n",dp[1][m]); return 0;}
后记
是从前往后 d p \tt dp dp 的,听上去很离谱,对吧?
其实它是这样的:在从右往左走的过程中,后面的房间会剩下 b i b_i bi 个人,这是成长的代价。那么我们从左往右,把 b i b_i bi 个人放好,就行了。(本质是 操作可逆,时光倒流,如同。)
发表评论
最新留言
关于作者
