题解: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;
}
posted @ 2025-08-11 10:49  TBSF_0207  阅读(7)  评论(0)    收藏  举报