SPOJ SP10622 DIFERENC - DIFERENCIJA 题解
Description
求 \(\sum\limits_{i=1}^{n}\sum\limits_{j=i}^{n}(\max\limits_{k=i}^{j}a_k-\min\limits_{k=i}^{j}a_k)\)。
其中 \(n\le3\times 10^{5}\)。
Solution
解法一
\(O(n^2)\) 的朴素算法是很容易想到的:首先外层循环枚举每个左端点,内层循环枚举右端点的同时维护区间内的最大值和最小值即可。
这样的朴素算法很难继续优化,主要是因为枚举的是区间。区间个数太多并且很难一起处理。于是可以考虑枚举每一个数的贡献。
显然有
因此原问题可以分成最大值之和与最小值之和两个几乎等价的子问题,这里只讨论最大值的部分。对于每个数 \(a_i\),能产生贡献的区间只有 \([l_i+1,r_i-1]\),其中 \(l_i\) 表示 \(a_i\) 左边第一个大于 \(a_i\) 的数的下标,\(r_i\) 表示 \(a_i\) 右边第一个大于 \(a_i\) 的数的下标。在这个区间内所有包含 \(a_i\) 的子区间的区间最大值都是 \(a_i\)。这样的子区间共有 \((i-l_i)\times(r_i-i+1)\) 个,因此 \(a_i\) 能产生的贡献就是 \(a_i\times(i-l_i)\times(r_i-i+1)\)。使用单调栈即可在 \(O(n)\) 的时间内求出 \(l_i\) 和 \(r_i\)。
最小值的部分同理。
时间复杂度 \(O(n)\)。
解法二
考虑动态规划。这里我们仍然是只讨论最大值的部分。设 \(f_i\) 表示所有以 \(a_i\) 结尾的子序列中的最大值的和。状态转移方程:
其中 \(l_i\) 表示 \(a_i\) 左边第一个大于 \(a_i\) 的数的下标。如果不存在这样的数,\(l_i\) 就等于 \(0\)。根据 \(l_i\) 的定义,显然有 \(a_i=\max\limits_{j=l_i+1}^{i}a_j\),也就是说,所有左端点位于 \([l_i+1,i]\),右端点为 \(i\) 的子序列中的最大值都是 \(a_i\),\(a_i\) 的贡献就是 \((i-l_i)\times a_i\)。由于 \(a_i<a_{l_i}\),\(a_i\) 不会对左端点位于 \([1,l_i]\) 的子序列产生任何影响,那一部分的贡献就是 \(f_{l_i}\)。
在枚举 \(i\) 的同时用单调栈维护一下 \(l_i\) 即可。最小值的部分同理。
时间复杂度 \(O(n)\)。
这里给出了解法二的代码实现。
Code
#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int N=3e5+10;
const int INF=0x3f3f3f3f;
int a[N];
int sta[N],top;
LL f[N];
int main()
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
top=0,a[0]=INF;
LL ans=0;
for(int i=1;i<=n;i++)
{
while(a[i]>=a[sta[top]]) top--;
LL x=sta[top];
f[i]=f[x]+(i-x)*a[i],ans+=f[i];
sta[++top]=i;
}
top=0,a[0]=0;
memset(f,0,sizeof(f));
for(int i=1;i<=n;i++)
{
while(a[i]<=a[sta[top]]) top--;
LL x=sta[top];
f[i]=f[x]+(i-x)*a[i],ans-=f[i];
sta[++top]=i;
}
printf("%lld\n",ans);
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?