[HEFFFF赛]瓶瓶大作战
发布日期:2021-05-07 01:07:02 浏览次数:25 分类:原创文章

本文共 3712 字,大约阅读时间需要 12 分钟。

题目

题目背景
“一次一瓶不是很正常吗?”——瓶瓶牛

题目描述
n n n 个房间,编号为 1 1 1 n n n 。有 n − 1 n-1 n1 个铁门,第 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 n103 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
    1. 如果 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
    2. 否则,只需要 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 x1a1
    1. 如果 f ( x 2 + x 1 − a 1 ) ≥ b 1 f(x_2+x_1-a_1)\ge b_1 f(x2+x1a1)b1,蕴含在 右边把左边接过去 的情况中,不用考虑了。
    2. 无论如何, f ( x 2 + x 1 − a 1 ) < m − a 1 f(x_2+x_1-a_1)<m-a_1 f(x2+x1a1)<ma1 是有必要的。

其中 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+x1a1) 是少算了 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(ma1,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 ma1+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(ma1,b1)]+a1 即第一个房间 a 1 a_1 a1 个人,第二个房间的人不足以打开门,而一号房间的人打开门之后,第二个房间的人又不足 m − a 1 m-a_1 ma1,没法逃生。

显然 m ≥ max ⁡ ( a i + b i ) m\ge\max(a_i+b_i) mmax(ai+bi) 无益。复杂度 O ( n ⋅ max ⁡ ( a i + b i , m ) ) \mathcal O(n\cdot\max(a_i+b_i,m)) O(nmax(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 个人放好,就行了。(本质是 操作可逆,时光倒流,如同。)

上一篇:[F**KTASK]环形划分
下一篇:[HEFFFF赛]Turn All The Lights On

发表评论

最新留言

初次前来,多多关照!
[***.217.46.12]2025年03月30日 08时45分25秒