E. Two Tanks
链接:E. Two Tanks
推荐大佬的视频讲解:B站 yingluosanqian
大晚上琢磨jiang老师的代码看不明白,还好找到这个视频讲解,以下题解基于该视频讲解,仅稍作个人理解,建议大家先看视频有配图更直观。
题意
给定两个水桶
A
,
B
A, B
A,B 容量分别为
a
,
b
a,b
a,b,以及
n
n
n 个操作
v
i
v_{i}
vi。当
v
i
>
0
v_{i} > 0
vi>0 表示从
A
A
A 中倒
v
i
v_{i}
vi 升水到
B
B
B,
v
i
<
0
v_{i} < 0
vi<0 则代表从
B
B
B 中倒水进
A
A
A。 倒水过程中水不会超过瓶子容量,若满则停,也不会倒出超过瓶子中已有的水量,至多倒空。
求当两个瓶子中初始水量分别为
c
,
d
c, d
c,d 时
A
A
A 中的最终水量,其中
0
<
=
c
<
=
a
0<=c<=a
0<=c<=a,
0
<
=
d
<
=
b
0<=d<=b
0<=d<=b。共有
(
a
+
1
)
∗
(
b
+
1
)
(a + 1) * (b + 1)
(a+1)∗(b+1) 种可能,用矩阵表示。
题解
先将倒水的问题抽象出来:
互相倒水的操作可以看做是一个
[
−
c
,
d
]
[-c, d]
[−c,d] 的线段在区间
[
−
a
,
b
]
[-a, b]
[−a,b] 中做平移运动,运动时必须与
0
0
0 有交点,也不能超出区间
[
−
a
,
b
]
[-a, b]
[−a,b]。
v
i
>
0
v_{i} > 0
vi>0 是将线段整体向右平移,
v
i
<
0
v_{i} < 0
vi<0 是将线段整体向左移,而倒水的那些限制也能体现,只能在区间内运动是水量不能超出瓶子的容量,必须与
0
0
0 有交点是水量为
x
x
x 时不能倒出超过
x
x
x,至多倒空。
这里放一张上文所说的视频up主的讲解片段,图中的即为所描述的抽象结果,绿线为
0
0
0 端点。
首先按
c
+
d
c + d
c+d 的总量来分类讨论,对于 第一个瓶子的初始水量设为
c
c
c,根据上述线段运动的描述可以想象到,存在一个区间
[
l
,
r
]
[l, r]
[l,r], 当
c
c
c 处在这个区间内时可以完整执行所有的
n
n
n 个操作而不会触发这些限制。 可能存在
l
>
r
l > r
l>r 的情况,说明不存在这样区间。
于是我们的情况有:
- l < = c < = r l <= c <= r l<=c<=r: 直接记录 n n n 个操作的前缀和 a n s = c − p r e ans = c - pre ans=c−pre,若不存在 [ l , r ] [l, r] [l,r] 这样的区间就一定是下面两种情况之一。
-
c
<
=
l
c <= l
c<=l:肯定存在某次操作会使得向右的操作受到限制 (线段左端点已经运动到
0
0
0 / 线段右端点已经运动到
b
b
b)。
那么这次操作过后的线段在区间的位置会和 c = l c = l c=l 的情况相同,于是最终答案输出 c = l c = l c=l 的即可。 - c > = r c >= r c>=r:同理一样会存在某个操作使得向左的操作收到限制,最终答案与 c = r c = r c=r 相同。
关于如何求区间
[
l
,
r
]
[l, r]
[l,r], 可以求出最终可能的最小水量和最大水量,我们设极限情况下线段经过
n
n
n 个操作后恰好达到上下界。
于是我们按这两个极限情况逆推
n
n
n 个操作回去,就能得到操作不受限制的上下界。
具体实现看代码以及注释
代码
时间有点晚,照着jiang老师的代码打了一遍,但加上了自己的理解注释,基本和jiang老师的相同,或许可以先看看大佬的代码:jiangly的代码
自己的代码:
#include <bits/stdc++.h>
using namespace std;
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr); cout.tie(nullptr);
int n, a, b;
cin >> n >> a >> b;
vector<int> v(n);
for (int i = 0; i < n; i ++) {
cin >> v[i];
}
// 设 A 为第一个水瓶,B 为第二个水瓶
vector< vector<int> > ans(a + 1, vector<int>(b + 1));
for (int s = 0; s <= a + b; s ++) { // 枚举初始A,B水量之和
/* n 次操作后 A 中水量的上下界 */
int x = max(0, s - b); // A 可能的最小水量
int y = min(a, s); // 可能的最大水量
/* 用上下界模拟逆过程找到 [l, r] */
int lo = x, hi = y;
for (int i = n - 1; i >= 0; i --) {
lo += v[i];
hi += v[i];
lo = max(lo, x); // 逆过程时进行修正,保证不会超过上下界
hi = min(hi, y);
}
// 逆过程结束后就得到最终的题解中所说的 [l, r]
/* 再用所得[l, r]进行一遍正向操作 */
int flo = lo, fhi = hi; // 可以得到初始分别为两个边界的最终结果
for (int i = 0; i < n; i ++) {
flo = max(min(flo - v[i], y), x);
fhi = max(min(fhi - v[i], y), x);
}
for (int c = x; c <= y; c ++) {
int d = s - c;
if(c <= lo) { // 小于等于 l 的
ans[c][d] = flo;
}
else if (c >= hi) { // 大于等于 r 的
ans[c][d] = fhi;
}
else { // 存在区间[l, r]
ans[c][d] = flo + (c - lo); // 区间最小值l的答案 + 超过l的部分
}
}
}
for(int c = 0; c <= a; c ++) {
for(int d = 0; d <= b; d ++) {
cout << ans[c][d] << " ";
}
cout << "\n";
}
return 0;
}