题解:P10318 [SHUPC 2024] 彭罗斯水槽
szh_AK_all · · 题解
为什么楼下题解写的那么麻烦,我来一篇简单易懂的题解。
首先由于水槽是环形排列的,所以为了方便起见,将原序列复制一遍,这样便将环展开了。
部分代码
int n;
cin >> n;
for (int i = 1; i <= n; i++)
cin >> a[i];
for (int i = n + 1; i <= n + n; i++)
a[i] = a[i - n];
接下来定义两个数组
考虑如何求得
对于边界情况,即
for (int i = 2; i <= n; i++) {
for (int j = i - 1; j >= 1; j--) {
if (a[j] < a[i]) {
l[i] = j;
break;
}
}
}
那么这种暴力方法的时间输在了哪里呢?假设水槽序列为 1 10000000 100000000 4 3 2,那么在求答案过程中,第
如上的例子便启发我们,可以尽量避开一些对答案不会有影响的位置。假设目前考虑到了第
反过头来看看
求得
复杂度是均摊的。
部分代码:
l[1] = 0;
for (int i = 2; i <= n + n; i++) {
int x = i - 1;
while (a[x] >= a[i] && x != 0)
x = l[x];
l[i] = x;
}//预处理左边第一个比a[i]小的
r[n + n] = n + n + 1;
for (int i = n + n - 1; i >= 1; i--) {
int x = i + 1;
while (a[x] > a[i] && x != 0 && x != n + n + 1)
x = r[x];
r[i] = x;
}//预处理右边第一个不比a[i]大的
接下来便是真正求答案的时刻了。
可以依次枚举每个位置对总答案产生的贡献,先来看个例子(也就是样例一)。
其中,黑色箭头指的是当前所考虑的
| 假设水溢出的值为其贡献值,则在计算每一秒所有水槽的水量总和时也就是用水起初的总量减去这一秒水的总贡献值。那么对于绿色区域,在第 |
数组/下标 | |||||
|---|---|---|---|---|---|---|
| 总贡献值变化数组 | ||||||
| 差分 |
||||||
| 差分 |
这样看便很直观了。
考虑如何求
可以发现,矩形的长为
再来思考下
Code
#include <bits/stdc++.h>
using namespace std;
#define int long long
int a[200005];
int l[200005], r[200005];
int ans[200005];
signed main() {
int n;
cin >> n;
for (int i = 1; i <= n; i++)
cin >> a[i];
for (int i = n + 1; i <= n + n; i++)
a[i] = a[i - n];
l[1] = 0;
for (int i = 2; i <= n + n; i++) {
int x = i - 1;
while (a[x] >= a[i] && x != 0)
x = l[x];
l[i] = x;
}//预处理左边第一个比a[i]小的
r[n + n] = n + n + 1;
for (int i = n + n - 1; i >= 1; i--) {
int x = i + 1;
while (a[x] > a[i] && x != 0 && x != n + n + 1)
x = r[x];
r[i] = x;
}//预处理右边第一个不比a[i]大的
int tmp = 0;
int sum = 0;
for (int i = 1; i <= n; i++) {
if (a[i] < a[tmp] || tmp == 0)
tmp = i;
sum += a[i];
}//找到高度最小的
for (int i = tmp + 1; i <= tmp + n; i++) {
if (a[i] == a[tmp])
continue;
//长方形的长:r[i]-l[i]-1;长方形的宽:a[i]-max(a[l[i]]+1,a[r[i]]+1)+1
int chang = r[i] - l[i] - 1, kuan = a[i] - max(a[l[i]] + 1, a[r[i]] + 1) + 1;
ans[1] += kuan;
ans[chang + 1] -= kuan;
}
for (int i = 1; i <= n; i++)
ans[i] += ans[i - 1];
for (int i = 1; i <= n; i++)
ans[i] += ans[i - 1];
for (int i = 1; i <= n; i++)
cout << sum - ans[i] << " ";
}
当然还有几个细节:
-
由于要计算每个水槽的贡献,所以应从长度为
2n 的序列中截取长度为n 的子段。设子段的左端点是left ,右端点是left+n-1 ,则应让a_{left-1} 最小,也代表着a_{left+n-1} 最小。使得遇到的r_i\le left\le 2n(a_i\ne a_{left-1}) ,做到不错算贡献值。 -
若
a_i=a_{left-1} ,则直接跳过该点。
路过的各位请给个赞吧!