1 条题解

  • 0
    @ 2025-8-24 22:56:52

    自动搬运

    查看原文

    来自洛谷,原作者为

    avatar szh_AK_all
    S挂分挂到被洛谷7级勾卡线|I can do all things

    搬运于2025-08-24 22:56:52,当前版本为作者最后更新于2024-05-06 12:59:27,作者可能在搬运后再次修改,您可在原文处查看最新版

    自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多

    以下是正文


    为什么楼下题解写的那么麻烦,我来一篇简单易懂的题解。

    首先由于水槽是环形排列的,所以为了方便起见,将原序列复制一遍,这样便将环展开了。

    部分代码

    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,rl,r,其中 lil_i 表示左边第一个高度比 aia_i 小的水槽的位置,rir_i 表示右边第一个高度不比 aia_i 大的水槽的(这样定义的用处见下文可知,并且这样定义容易求出 l,rl,r 数组)。

    考虑如何求得 ll 数组。

    对于边界情况,即 i=1i=1 时,显然,由于 ii 左边没有水槽了,所以 li=0l_i=0;那么对于 i>1i>1 的情况,首先可以得到如下暴力代码:

    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,那么在求答案过程中,第 2,32,3 个位置会经过多次,但是显然,凭借肉眼观察也能得知,第 2,32,3 个位置的水槽的高度太大了,对 ll 数组不会有贡献。

    如上的例子便启发我们,可以尽量避开一些对答案不会有影响的位置。假设目前考虑到了第 i1i-1 个位置的水槽的高度是否小于第 ii 个位置的水槽的高度,则分为两种情况:一、ai1<aia_{i-1}<a_{i},则直接更新 lil_i 即可;二、ai1aia_{i-1}\ge a_{i},则开始考虑避开一些对答案不会有影响的位置。

    反过头来看看 ll 的定义:lil_i 表示左边第一个高度比 aia_i 小的水槽的位置,那这是不是代表第 i1i-1 至第 li+1l_i+1 个位置的水槽的高度都是不小于第 ii 个位置的水槽的高度?显然是的,又因为 ai1aia_{i-1}\ge a_i,所以 akai(i1kai1)a_k\ge a_i(i-1\ge k\ge a_{i-1}),也就是说,第 i1i-1 至第 li+1l_i+1 个位置的水槽对答案没有影响。那么可以一个个跳 jj,若 aj<aia_j<a_i,则更新 lil_i;否则,j=ljj=l_j

    求得 rr 数组的方法是类似的。

    复杂度是均摊的。

    部分代码:

    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]大的
    

    接下来便是真正求答案的时刻了。

    可以依次枚举每个位置对总答案产生的贡献,先来看个例子(也就是样例一)。

    其中,黑色箭头指的是当前所考虑的 ii66,黄色箭头指的是 li=2l_i=2,蓝色箭头指的是 ri=7r_i=7,那么,可以发现,仅有绿色区域的水会在第 rir_i 个位置溢出,且这个绿色区域恰好是个矩形。那么,根据绿色区域的贡献,在第 11 秒,水会溢出 11(也就是矩形的宽),在第 22 秒水会再溢出 11,直到第 44(也就是矩形的长)秒,水一直会溢出 11。所以可以归纳出做法:

    假设水溢出的值为其贡献值,则在计算每一秒所有水槽的水量总和时也就是用水起初的总量减去这一秒水的贡献值。那么对于绿色区域,在第 11 至第 44 秒,水的贡献值会逐渐增加一,这正好可以用二阶差分来处理。下面列出表格,方便观察差分数组(设 cc 为矩形的长,kk 为矩形的宽)。 | 数组/下标 | 11 | 22 | \dots | cc | c+1c+1 | | :-----------: | :-----------: | :-----------: | :-----------: | :-----------: | :-----------: | |总贡献值变化数组 |+k+k | +2k+2k | \dots | +ck+ck | +ck+ck | | 差分 11 | +k+k | +k+k |+k+k | +k+k| +0+0| |差分 22| +k+k | +0+0| +0+0 |+0+0 | k-k|

    这样看便很直观了。

    考虑如何求 c,kc,k

    可以发现,矩形的长为 rili1r_i-l_i-1(范围是 li+1l_i+1ri1r_i-1),那么宽呢(也可以看成是高)?观察图片可知,在绿色区域上方的 33 格水并不会在 rir_i 这个位置溢出,也就是说,这个绿色区域的左右两边的高度应该是相同的(正好印证了绿色区域是个矩形),而绿色区域的宽则要最大化左右两边相同的高度(因为水可以溢出便一定会溢出)。

    再来思考下 ll 数组的特性,在第 li+1l_i+1 至第 i1i-1 个位置的水槽的高度不小与 aia_i,而因为绿色区域的左边部分在 lil_i 的右上角,所以这也就代表了绿色区域的最低部分的高度高于 alia_{l_i};同理,对于 rr 数组,可以得知绿色区域的最低部分的高度高于 aria_{r_i}。由于 aia_i 是在 ali+1,li+2,ri1a_{l_{i}+1,l_{i}+2,\dots r_i-1} 中最小的,所以这代表绿色区域的最高部分不超过 aia_i,由此,可以算出 kk

    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] << " ";
    }
    

    当然还有几个细节:

    • 由于要计算每个水槽的贡献,所以应从长度为 2n2n 的序列中截取长度为 nn 的子段。设子段的左端点是 leftleft,右端点是 left+n1left+n-1,则应让 aleft1a_{left-1} 最小,也代表着 aleft+n1a_{left+n-1} 最小。使得遇到的 rileft2n(aialeft1)r_i\le left\le 2n(a_i\ne a_{left-1}),做到不错算贡献值。

    • ai=aleft1a_i=a_{left-1},则直接跳过该点。

    路过的各位请给个赞吧!

    • 1

    信息

    ID
    10015
    时间
    1000ms
    内存
    256MiB
    难度
    5
    标签
    递交数
    0
    已通过
    0
    上传者