链接: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 , r ] [l, r] [l,r], 可以求出最终可能的最小水量和最大水量,我们设极限情况下线段经过 n n n 个操作后恰好达到上下界。
于是我们按这两个极限情况逆推 n n n 个操作回去,就能得到操作不受限制的上下界。
具体实现看代码以及注释
时间有点晚,照着jiang老师的代码打了一遍,但加上了自己的理解注释,基本和jiang老师的相同,或许可以先看看大佬的代码:jiangly的代码
自己的代码:
#include
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;
}