题解:P10194 [USACO24FEB] Milk Exchange G
提供一种比较易懂但是常数大一点的单调栈+二次差分写法。
我们先破环成链,将序列自我复制一次。
形式化的,要求的答案 \(ans_k=\sum_{i=1}^n \min_{j=i}^{i+k} a_j\)。
发现如果每个区间贡献单独考虑好像不太可做,那么可以考虑每个 \(a_j\) 对答案的贡献。
考虑在一个无限自我复制的序列中,有一个位置 \(x\),它左侧最近且满足 \(a_{L-1}\le a_x\) 的位置为 \(L\),它右侧最近且满足 \(a_{R+1}<a_x\) 的位置为 \(R\),很显然两者都可以用单调栈维护。
然后我们记 \(L_i\in [L,x]\)。
然后考虑 \(a_x\) 的贡献。我们将所有时间向后推一秒。显然地从一个 \(j\) 到达 \(i(i>j)\) 的时刻是 \(i-j+1\)。那么对于 \(i\) 来说,\(a_x\) 在时间轴上的贡献就相当于 \([x-L_i +1,R-L_i +1]\)。
这样我们就得到了一个 \(a_x\) 对时间的贡献,那就是在时间轴 \([x-L+1,R-L+1]\) 一直到 \([1,R-x+1]\) 的等长区间上每一个区间区间加上 \(a_x\) 的结果。
这个东西怎么维护呢?对于一个静态区间加很容易想到差分,那若干个区间加就相当于在差分数组上再区间加。可以对差分数组再做一次差分,就相当于在每个区间开头分别加上一个值,在每个区间结尾 \(+1\) 的位置分别减去一个值。那么直接在二次差分数组上维护即可。
最后是为什么 \(L,R\) 的边界要这么设置,其实是为了防止重复。对于一个位置 \(x\),如果有 \(y<x,a_y=a_x\),那么 \(y\) 前面的贡献一定被计算过了,\(L=y+1\) 即可。
总体时间复杂度 \(O(n)\)。
在代码实现中,由于假想是无限自我复制的序列,所以需要自复制三遍然后在最中间的序列进行答案累加。
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=5e5+5;
int n,a[3*N],L[3*N],R[3*N];
stack<int>stk;
LL ans[N*3];
int main(){
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin>>n;
for(int i=1;i<=n;i++)
cin>>a[i];
for(int i=n+1;i<=3*n;i++)
a[i]=a[i-n];
for(int i=1;i<=n*3;i++){
while(!stk.empty()&&a[stk.top()]>a[i])stk.pop();
if(stk.empty())L[i]=1;
else L[i]=stk.top()+1;
stk.push(i);
}
while(!stk.empty())stk.pop();
for(int i=n*3;i>=1;i--){
while(!stk.empty()&&a[stk.top()]>=a[i])stk.pop();
if(stk.empty())R[i]=n*3;
else R[i]=stk.top()-1;
stk.push(i);
}
for(int i=n+1;i<=2*n;i++){
ans[1]+=a[i];
ans[i-L[i]+2]-=a[i];
ans[R[i]-i+2]-=a[i];
ans[R[i]-L[i]+3]+=a[i];
}
for(int i=2;i<=n+1;i++)
ans[i]+=ans[i-1];
for(int i=2;i<=n+1;i++)
ans[i]+=ans[i-1],cout<<ans[i]<<'\n';
return 0;
}

浙公网安备 33010602011771号