CodeJam 2019 Round 3 Rancake Pyramid
- problem
- solution
- code
problem
神奈子是个很爱打麻将的老婆婆,有一天她把她的麻将放成了 nnn 堆,第 iii 堆的高度为 aia_iai 。
因为她很喜欢风,所以她用风吹倒了最左边的 LLL 堆麻将和最右边的 RRR 堆麻将,但是她也不想吹倒太多,所以剩下的麻将至少有 333 堆。
因为她很喜欢山,所以她希望剩下的麻将能形成山峰一样的形状。
设剩下的麻将形成的序列为 b1,…,bkb_1,…,b_kb1,…,bk,她希望存在至少一个 i∈[1,k]i\in[1,k]i∈[1,k] 满足 b1≤b2≤...≤bi≥bi+1≥...≥bkb_1\le b_2\le ...\le b_i\ge b_{i+1}\ge ...\ge b_kb1≤b2≤...≤bi≥bi+1≥...≥bk。
然而她发现很多时候麻将并不是山峰的样子,所以她会为若干堆麻将增高一些高度,她不想花太多时
间,所以她会选择每一堆麻将增高的高度之和最小的方法。
她非常无聊,所以想尝试所有情况,即对于每对满足 0≤L,R≤n,L+R≤n−30\le L,R\le n,L+R\le n-30≤L,R≤n,L+R≤n−3 的 (L,R)(L,R)(L,R) 都进行一次上述过程。
每次完成一次操作后她都会把麻将还原成初始状态,现在她想知道在全部情况下增加的麻将高度之和是多少,答案可能过大,对 1e9+71e9+71e9+7 取模。
3≤n≤106,1≤ai≤1093\le n\le 10^6,1\le a_i\le 10^93≤n≤106,1≤ai≤109。
solution
有个很重要的结论:对应一段区间操作,一定是选择最高的为山峰,如果有多个相同的随便选一个就行。
证明:这是很显然的。不是很严谨的理解一下。
因为如果最高的不是山峰,那么被选成山峰的点自己都需要被拔高到至少是最高点的高度。
然后被选做山峰的点和最高的点之间的所有点都要被拔高到至少最高点的高度。
显然不如选最高做山峰,然后同样的一段就不需要被拔高到至少最高点的高度。
暴力的 O(n2)O(n^2)O(n2) 我在考场上写出来,结果内存算错了,define int long long
MLE 挂了qwq
就是枚举 L,RL,RL,R ,然后得到剩下的还立着的麻将区间 [l,r][l,r][l,r] ,考虑怎么 O(1)O(1)O(1) 计算。
首先要能快速知道区间内的最高点位置 ppp ,st表
预处理即可。
l,rl,rl,r 从最高点分开的左右区间是相同的对称问题,且被最高点分开,相互独立,接下来操作以 lll 为例。
lll 端点作为开始肯定是可以不用改变的,然后判断 l+1l+1l+1 与 lll 的麻将高度,如果 al+1≥ala_{l+1}\ge a_lal+1≥al 那就不用拔高,否则贪心地只需要拔高到 ala_lal 的高度就行了。这样不仅拔高高度最小化,并且增加了后面不拔高的可能性。
肯定的,不可能每次都扫一遍区间,那是 O(n3)O(n^3)O(n3) 的了。
发现可以通过维护前缀信息来 O(1)O(1)O(1) 计算每个区间对应的答案。
-
preh[i]
:表示从 111 开始,iii 最后的高度。具体而言:
- 如果不拔高,即 preh[i−1]≤a[i]preh[i-1]\le a[i]preh[i−1]≤a[i],则 preh[i]=a[i]preh[i]=a[i]preh[i]=a[i]
- 如果拔高,即 preh[i−1]>a[i]preh[i-1]>a[i]preh[i−1]>a[i],则 preh[i]=preh[i−1]preh[i]=preh[i-1]preh[i]=preh[i−1]
-
pres[i]
:表示从 111 开始维护到 iii 为止一共拔高的高度。具体而言:
- 如果不拔高,则 pres[i]=pres[i−1]pres[i]=pres[i-1]pres[i]=pres[i−1]
- 如果拔高,则 pres[i]=pres[i−1]+preh[i]−a[i]pres[i]=pres[i-1]+preh[i]-a[i]pres[i]=pres[i−1]+preh[i]−a[i]
每次区间 [l,r][l,r][l,r] 是从 lll 开始的,所以要减去前 l−1l-1l−1 个的拔高影响,pres[i]−pres[l−1]pres[i]-pres[l-1]pres[i]−pres[l−1]。
但是注意到,这段区间可能 lll 是被拔高了的,所以要再判断一下然后减去。
具体而言:
如果 a[l]=preh[l]a[l]=preh[l]a[l]=preh[l],证明从 111 开始 lll 是没有被拔高的。
那么 pres[i]−pres[l−1]pres[i]-pres[l-1]pres[i]−pres[l−1] 就是 [l,r][l,r][l,r] 从 lll 开始拔高的贡献。
如果 a[i]<preh[l]a[i]<preh[l]a[i]<preh[l],证明从 111 开始 lll 是被拔高过的。
但是 [l,r][l,r][l,r] 从 lll 开始就不需要拔高,所以 preh[l]−a[l]preh[l]-a[l]preh[l]−a[l] 是后面一段多被拔高的高度的。
要减去,但也不是单独的乘以区间长度,因为有些点不一定被拔高了。
所以还要维护一个
prec[i]
:表示从 111 开始维护到 iii 为止被拔高过的麻将个数。减去的应该为:(preh[l]−a[l])(prec[p]−prec[l−1])(preh[l]-a[l])(prec[p]-prec[l-1])(preh[l]−a[l])(prec[p]−prec[l−1])
后缀同理维护,sufh[i]
sufs[i]
sufc[i]
。
因为伞兵博主写代码时直接原地覆盖,导致n^2暴力的代码彻底没了。所以就不能提供给大家了┏┛墓┗┓…(((m -__-)m
暴力是抓住左右端点不放。然而一般具有性质的特殊位置,比如最大点位置,一堆区间然后有一个特殊位置等等,正解都是考虑枚举这个特殊位置的。
所以这里考虑枚举每一个麻将做一个区间的最大值的答案。
假设一个麻将 iii 是一段区间 [l,r][l,r][l,r] 的最大值,即 a[l−1],a[r+1]a[l-1],a[r+1]a[l−1],a[r+1] 都比 iii 麻将高度高。
那么对于 ∀l≤x≤i≤y≤r,[x,y]\forall\ l\le x\le i\le y\le r,[x,y]∀ l≤x≤i≤y≤r,[x,y] 都是以 iii 为不变的最高麻将计算。
这样子就是笛卡尔树大根堆的区间限制,考虑左右儿子的贡献,然后再考虑节点本身的贡献。
这里也有正如前面遇到的问题,不同位置开始如果预处理要考虑被多拔高了的高度。
所以不妨一开始就将每个麻将的高度直接剪掉,麻将能在多少种区间内就被减去多少次。
后面直接加上最终高度,这中间的差就是被拔高高度,而且是一一对应的。
具体而言:
建立大根堆笛卡尔树,计算每个点作为最大值的贡献。
记录笛卡尔树上点 iii 的信息 suml,sumrsuml,sumrsuml,sumr 表示 iii 向左 / 右区间内每个点的答案之和,通过儿子快速计算。
跨越 iii 的答案为 suml∗(r−i+1)+sumr∗(i−l+1)suml*(r-i+1)+sumr*(i-l+1)suml∗(r−i+1)+sumr∗(i−l+1)。
以及 iii 点本身的答案,(i−l+1)∗(r−i+1)∗a[i](i-l+1)*(r-i+1)*a[i](i−l+1)∗(r−i+1)∗a[i]。
详情可见代码实现。
code
#include <bits/stdc++.h>
using namespace std;
#define maxn 1000005
#define int long long
#define mod 1000000007
int n, ans;
pair < int, int > MS[maxn];
bool vis[maxn];
int a[maxn], s[maxn], L[maxn], R[maxn];void solve( int p ) {vis[p] = 1;int l = p, r = p, suml = 0, sumr = 0;if( vis[p - 1] ) l = L[p - 1], suml = s[p - 1];if( vis[p + 1] ) r = R[p + 1], sumr = s[p + 1];ans = ( ans + ( p - l + 1 ) * sumr ) % mod;ans = ( ans + ( r - p + 1 ) * suml ) % mod;int w = a[p] * ( p - l + 1 ) % mod * ( r - p + 1 ) % mod;ans = ( ans + w ) % mod;L[r] = l, R[l] = r, s[l] = s[r] = ( suml + sumr + w ) % mod;
}signed main() {scanf( "%lld", &n );for( int i = 1;i <= n;i ++ ) {scanf( "%lld", &a[i] );ans = ( ans - i * ( n - i + 1 ) % mod * a[i] ) % mod;MS[i] = { a[i], i };}sort( MS + 1, MS+ n + 1 );for( int i = 1;i <= n;i ++ ) solve( MS[i].second );printf( "%lld\n", ( ans + mod ) % mod );return 0;
}