# P10194 [USACO24FEB] Milk Exchange G 题解

Table of Contents

开始打比赛只剩 33 h 了,晚了 11 min 提交,痛失铂金……

洛谷传送门

题意

有长度为 nn环形序列 aa,初始时 b=ab=a,每次操作令 bi=min(bi1,ai), i[1,n]b_i=\min(b_{i-1},a_i),\forall\ i \in [1,n]

求前 nn 次操作,每次操作后 bb 数组的和。

思路

这里使用 3 8 9 2 5 4 7 1 6 这个样例来进行模拟。

由于奶牛是排成一圈的,所以可以将容量最小的奶桶轮换到第一个。此时,牛奶的量变为 1 6 3 8 9 2 5 4 7

这时容易发现,第 ii 个奶桶的牛奶量在 jj 次操作后,会变成 mink=max(ij+1,1)i{ak}\min\limits_{k=\max(i-j+1,1)}^i \{a_k\}

这时候仍然不好观察,每次操作后每个奶桶的牛奶量变化,但是当我们把所有每次操作后牛奶量的表画出来时,就变得方便多了:

操作1\color{red}12\color{purple}23\color{yellow}34\color{brown}45\color{blue}56\color{green}67\color{gray}78\color{orange}89\color{pink}9
001\color{red}16\color{purple}63\color{yellow}38\color{brown}89\color{blue}92\color{green}25\color{gray}54\color{orange}47\color{pink}7
111\color{red}11\color{red}13\color{yellow}33\color{yellow}38\color{brown}82\color{green}22\color{green}24\color{orange}44\color{orange}4
221\color{red}11\color{red}11\color{red}13\color{yellow}33\color{yellow}32\color{green}22\color{green}22\color{green}24\color{orange}4
331\color{red}11\color{red}11\color{red}11\color{red}13\color{yellow}32\color{green}22\color{green}22\color{green}22\color{green}2
441\color{red}11\color{red}11\color{red}11\color{red}11\color{red}12\color{green}22\color{green}22\color{green}22\color{green}2
551\color{red}11\color{red}11\color{red}11\color{red}11\color{red}11\color{red}12\color{green}22\color{green}22\color{green}2
661\color{red}11\color{red}11\color{red}11\color{red}11\color{red}11\color{red}11\color{red}12\color{green}22\color{green}2
771\color{red}11\color{red}11\color{red}11\color{red}11\color{red}11\color{red}11\color{red}11\color{red}12\color{green}2
881\color{red}11\color{red}11\color{red}11\color{red}11\color{red}11\color{red}11\color{red}11\color{red}11\color{red}1

可以发现,除了 11 号奶桶容量在表中构成了一个完整的三角形,其它奶桶容量都构成了一个平行四边形。我们要求的答案,就是每行数的总和。

对于 ii 号桶对应的平行四边形,其对每一行产生的贡献,可以分为三段:一段公差为 aia_i 的等差数列,一段公差为 00 的等差数列,一段公差为 ai-a_i 的等差数列。而区间加等差数列是一个十分经典的问题,两次差分即可解决,如果不会请出门右转 P4231 三步必杀

显然,我们只要知道出 ii 号桶对应平行四边形的底(aa)和高(hh),就能计算出要加的是哪三段等差数列。下面考虑平行四边形的底和高如何计算。我们用 i=3i=3 号桶对应的平行四边形举例。

操作1{}12{}23{}34{}45{}56{}67{}78{}89{}9
001{\color{blue}{\blacksquare}}16{}63{\color{purple}{\blacksquare}}\color{yellow}38{}89{}92{\color{orange}{\blacksquare}}25{}54{}47{}7
111{}11{}13{\color{green}{\blacksquare}}\color{yellow}33\color{yellow}38{}82{}22{}24{}44{}4
221{}11{}11{\color{red}{\blacksquare}}13\color{yellow}33\color{yellow}32{}22{}22{}24{}4
331{}11{}11{}11{}13\color{yellow}32{}22{}22{}22{}2
441{}11{}11{}11{}11{}12{}22{}22{}22{}2
551{}11{}11{}11{}11{}11{}12{}22{}22{}2
661{}11{}11{}11{}11{}11{}11{}12{}22{}2
771{}11{}11{}11{}11{}11{}11{}11{}12{}2
881{}11{}11{}11{}11{}11{}11{}11{}11{}1

a=dis(,)=dis(,)1=dis(,)1=iprei=3pre3=2a=dis({\color{purple}{\blacksquare}},{\color{green}{\blacksquare}})=dis({\color{purple}{\blacksquare}},{\color{red}{\blacksquare}})-1=dis({\color{purple}{\blacksquare}},{\color{blue}{\blacksquare}})-1=i-pre_i=3-pre_3=2

d=dis(,)1=nexii=nex33=3d=dis({\color{purple}{\blacksquare}},{\color{orange}{\blacksquare}})-1=nex_i-i=nex_3-3=3

其中 preipre_inexinex_i 分别表示在 ii 之前最后一个小于等于 aia_i 的下标,以及在 ii 之后第一个小于 aia_i 的下标。这两个数组可以通过单调栈求得。

理由也很好解释:ii 号桶在 ipreii-pre_i 次操作后,就只能剩下 apreia_{pre_i} 升牛奶;ii 号桶的奶在传到 nexinex_i 时,就只能剩下 anexia_{nex_i} 升牛奶。

所以我们可以求出 ii 号桶对应的底为 ipreii-pre_i,对应的高为 nexiinex_i-i。然后就能求出三个区间并进行区间加等差数列的操作。

这里有一个小细节:为什么 preipre_i 算的是小于等于 aia_i,而 nexinex_i 算的是小于 aia_i。其实为了不重不漏地填数,两个必须是一个小于,一个小于等于。但由于我们开始时默认 11 号桶构成的是一个三角形,而如果 preipre_i 算的是小于 aia_i,那么若再碰到和 a1a_1 相等的 aia_i,就会加上一个超出表格的梯形,并且还会重复加一个单元格,所以 preipre_i 算的必须是小于等于。

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;//不开 long long 见祖宗
const ll N=5e5+5;
ll n,a[N<<1];
ll pre[N],nex[N];
ll mi=1e9,mik;
ll st[N],r;
ll p[N];//差分数组
void change(ll l,ll r,ll x,ll d){//将从 l 到 r 的数加上以 x 为首项,d 为公差的等差数列
if(l>r) return;
ll y=x+(r-l)*d;
p[l]+=x,p[l+1]-=x;
p[l+1]+=d,p[r+1]-=d;
p[r+1]-=y,p[r+2]+=y;
}
int main(){
#ifndef ONLINE_JUDGE
freopen("test.in","r",stdin);
freopen("test.out","w",stdout);
#endif
ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr);
cin>>n;
for(ll i=1;i<=n;i++){
cin>>a[i],a[i+n]=a[i];
if(mi>a[i]) mi=a[i],mik=i;
}
for(ll i=1;i<=n;i++) a[i]=a[i+mik-1];//将最小值移到第一个
r=0,st[r]=1;//单调栈求 pre 数组
for(ll i=2;i<=n;i++){
while(r>=1&&a[st[r]]>a[i]) r--;
pre[i]=st[r],st[++r]=i;
}
r=0,st[r]=n+1;//单调栈求 nex 数组
for(ll i=n;i>=2;i--){
while(r>=1&&a[st[r]]>=a[i]) r--;
nex[i]=st[r],st[++r]=i;
}
change(1,n,a[1],a[1]);
for(ll i=2;i<=n;i++){
ll A=i-pre[i],D=nex[i]-i;//平行四边形的底为 A,高为 D
if(A<=D){//分类讨论
change(1,A-1,a[i],a[i]);
change(A,D,a[i]*A,0);
change(D+1,A+D-1,a[i]*(A-1),-a[i]);
}
else{
change(1,D,a[i],a[i]);
change(D+1,A-1,a[i]*D,0);
change(A,A+D-1,a[i]*D,-a[i]);
}
}
for(ll i=1;i<=n;i++) p[i]+=p[i-1];//做两次前缀和还原数组
for(ll i=1;i<=n;i++) p[i]+=p[i-1];
for(ll i=2;i<=n;i++) cout<<p[i]<<"\n";
cout<<p[n]<<"\n";//n 次操作后的结果和 n-1 次操作的结果相同
return 0;
}