题目描述:
Mod 109
题目分析:
分治真是强大。。
计算 [ l , r ] [l,r] [l,r]的答案,只需要考虑怎么算跨过 m i d mid mid的答案,其余的分治即可。枚举左端点 i : m i d → l i:mid\rarr l i:mid→l,记 [ i , m i d ] [i,mid] [i,mid]的最小值为 m n mn mn,最大值为 m x mx mx,找到右边最后一个 ≥ m n \ge mn ≥mn的位置 j j j,以及最后一个 ≤ m x \le mx ≤mx的位置 k k k。
不妨设 j ≤ k j\le k j≤k,这时答案分成了三个部分:
- 右端点 ≤ j \le j ≤j, a n s = m n ∗ m x ∑ p = m i d + 1 j p − i + 1 ans=mn*mx\sum_{p=mid+1}^j{p-i+1} ans=mn∗mx∑p=mid+1jp−i+1,可直接计算。
-
j
<
j<
j<右端点
≤
k
\le k
≤k,
a
n
s
=
m
n
∗
∑
p
=
j
+
1
k
m
a
x
[
m
i
d
+
1
,
p
]
∗
(
p
−
i
+
1
)
=
m
n
∗
∑
p
=
j
+
1
k
m
a
x
[
m
i
d
+
1
,
p
]
∗
p
−
m
n
∗
∑
p
=
j
+
1
k
∗
m
a
x
[
m
i
d
+
1
,
p
]
∗
(
i
−
1
)
ans=mn*\sum_{p=j+1}^kmax[mid+1,p]*(p-i+1)=mn*\sum_{p=j+1}^kmax[mid+1,p]*p-mn*\sum_{p=j+1}^k*max[mid+1,p]*(i-1)
ans=mn∗∑p=j+1kmax[mid+1,p]∗(p−i+1)=mn∗∑p=j+1kmax[mid+1,p]∗p−mn∗∑p=j+1k∗max[mid+1,p]∗(i−1)
预处理 ∑ m a x [ m i d + 1 , p ] ∗ p \sum max[mid+1,p]*p ∑max[mid+1,p]∗p以及 ∑ m a x [ m i d + 1 , p ] \sum max[mid+1,p] ∑max[mid+1,p]即可。 -
k
<
k<
k<右端点
≤
r
\le r
≤r,
a
n
s
=
∑
p
=
k
+
1
r
m
i
n
[
m
i
d
+
1
,
p
]
∗
m
a
x
[
m
i
d
+
1
,
p
]
∗
(
p
−
i
+
1
)
ans=\sum_{p=k+1}^rmin[mid+1,p]*max[mid+1,p]*(p-i+1)
ans=∑p=k+1rmin[mid+1,p]∗max[mid+1,p]∗(p−i+1),同上面一样地拆分。
预处理 ∑ m i n [ m i d + 1 , p ] ∗ m a x [ m i d + 1 , p ] ∗ p \sum min[mid+1,p]*max[mid+1,p]*p ∑min[mid+1,p]∗max[mid+1,p]∗p以及 m i n [ m i d + 1 , p ] ∗ m a x [ m i d + 1 , p ] min[mid+1,p]*max[mid+1,p] min[mid+1,p]∗max[mid+1,p]即可。
随着 i : m i d → l i:mid\rarr l i:mid→l, j j j和 k k k单调不减,所以复杂度是 O ( r − l ) O(r-l) O(r−l)的,总复杂度就是 O ( n l o g n ) O(nlogn) O(nlogn)。
如果可以用 O ( r − l ) O(r-l) O(r−l)或 O ( ( r − l ) ∗ l o g ) O((r-l)*log) O((r−l)∗log)的时间,来解决跨越 m i d mid mid的答案,这时就可以考虑分治。
Code:
#include<cstdio>
#include<vector>
#include<cstring>
#include<algorithm>
#define maxn 500005
using namespace std;
const int mod = 1e9;
int n,a[maxn],ans;
int smn[maxn],SMN[maxn],smx[maxn],SMX[maxn],mnx[maxn],MNX[maxn];
void solve(int l,int r){
if(l==r) {ans=(ans+1ll*a[l]*a[r])%mod;return;}
int mid=(l+r)>>1;
solve(l,mid),solve(mid+1,r);
smn[mid]=SMN[mid]=smx[mid]=SMX[mid]=mnx[mid]=MNX[mid]=0;
for(int i=mid+1,mx=0,mn=1e9;i<=r;i++){
mx=max(mx,a[i]),mn=min(mn,a[i]);
smn[i]=(smn[i-1]+mn)%mod,SMN[i]=(SMN[i-1]+1ll*mn*i)%mod;
smx[i]=(smx[i-1]+mx)%mod,SMX[i]=(SMX[i-1]+1ll*mx*i)%mod;
mnx[i]=(mnx[i-1]+1ll*mn*mx)%mod,MNX[i]=(MNX[i-1]+1ll*mn*mx%mod*i)%mod;
}
for(int i=mid,j=mid,k=mid,mx=0,mn=1e9;i>=l;i--){
mx=max(mx,a[i]),mn=min(mn,a[i]);
while(j<r&&a[j+1]>=mn) j++;
while(k<r&&a[k+1]<=mx) k++;
int x=min(j,k),y=max(j,k);
ans=(ans+1ll*(mid+1-i+1+x-i+1)*(x-mid)/2%mod*mx%mod*mn)%mod;
ans=(ans+MNX[r]-MNX[y]-1ll*(mnx[r]-mnx[y])*(i-1))%mod;
if(j<k) ans=(ans+1ll*mx*(SMN[k]-SMN[j]-1ll*(smn[k]-smn[j])*(i-1)%mod))%mod;
else ans=(ans+1ll*mn*(SMX[j]-SMX[k]-1ll*(smx[j]-smx[k])*(i-1)%mod))%mod;
}
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
solve(1,n);
printf("%d\n",(ans+mod)%mod);
}
还有大佬用线段树的做法。大致思路就是枚举右端点 r : 1 → n r:1\rarr n r:1→n,计算左端点的答案,每次移动右端点都会对最小值或最大值进行区间修改,同样要维护 m i n [ l , r ] ∗ m a x [ l , r ] ∗ l min[l,r]*max[l,r]*l min[l,r]∗max[l,r]∗l和 m i n [ l , r ] ∗ m a x [ l , r ] min[l,r]*max[l,r] min[l,r]∗max[l,r],然后线段树修改,大致思路就是这样。这里有一篇用线段树写的blog,不确定是不是一样的,姑且看一看。