这题解不是口胡的。
花絮
一个月前看这题,早上看了,准备在学校中自习写完作业想一会儿,结果推了一整个中自习加上一个课件加上一堂课都没推出来……想得非常复杂,在那里写了个多项式然后什么NTT,FFT都搞出来了,结果最后复杂度与暴力没什么差别……自闭了……
Solution
不推式子怎么行?
∑ l = 1 n ∑ r = l n f ( l , r ) \sum_{l=1}^n \sum_{r=l}^n f(l,r) ∑l=1n∑r=lnf(l,r)
= ∑ l = 1 n ∑ r = l n ∑ k = l r a k ( c n t ( l , r , a k ) + 1 ) =\sum_{l=1}^n \sum_{r=l}^n \sum_{k=l}^r a_k\ (cnt(l,r,a_k)+1) =∑l=1n∑r=ln∑k=lrak (cnt(l,r,ak)+1)
这里 c n t ( l , r , x ) cnt(l,r,x) cnt(l,r,x)表示区间 [ l , r ] [l,r] [l,r]中比 x x x小的数的个数。紧接着,使用前缀和的技巧:
= ∑ l = 1 n ∑ r = l n ∑ k = l r a k ( c n t ( 1 , r , a k ) − c n t ( 1 , l − 1 , a k ) + 1 ) =\sum_{l=1}^n \sum_{r=l}^n \sum_{k=l}^r a_k\ (cnt(1,r,a_k)-cnt(1,l-1,a_k)+1) =∑l=1n∑r=ln∑k=lrak (cnt(1,r,ak)−cnt(1,l−1,ak)+1)
使用乘法分配律:
= [ ∑ l = 1 n ∑ r = l n ∑ k = l r a k ( c n t ( 1 , r , a k ) ] − [ ∑ l = 1 n ∑ r = l n ∑ k = l r a k c n t ( 1 , l − 1 , a k ) ] + [ ∑ l = 1 n ∑ r = l n ∑ k = l r a k ] =[\sum_{l=1}^n \sum_{r=l}^n \sum_{k=l}^r a_k\ (cnt(1,r,a_k)]-[\sum_{l=1}^n \sum_{r=l}^n \sum_{k=l}^r a_k\ cnt(1,l-1,a_k)]+[\sum_{l=1}^n \sum_{r=l}^n \sum_{k=l}^r a_k] =[∑l=1n∑r=ln∑k=lrak (cnt(1,r,ak)]−[∑l=1n∑r=ln∑k=lrak cnt(1,l−1,ak)]+[∑l=1n∑r=ln∑k=lrak]
于是这个式子就被拆成了三个部分,我们一个部分一个部分地解决。
Part 1
= ∑ l = 1 n ∑ r = l n ∑ k = l r a k ( c n t ( 1 , r , a k ) =\sum_{l=1}^n \sum_{r=l}^n \sum_{k=l}^r a_k\ (cnt(1,r,a_k) =∑l=1n∑r=ln∑k=lrak (cnt(1,r,ak)
这里的 l l l似乎并没有什么用……同时交换里面两个 ∑ \sum ∑:
= ∑ k = 1 n a k k ∑ r = k n c n t ( 1 , r , a k ) =\sum_{k=1}^n a_k k \sum_{r=k}^n cnt(1,r,a_k) =∑k=1nakk∑r=kncnt(1,r,ak)
这里面要乘上一个 k k k,是因为包含 k k k的左端点有 k k k种。
第一部分的式子推到这里已经可以了。我们枚举 k k k,虽然 a k k a_kk akk很容易算,但是里面的那个 ∑ r = k n c n t ( 1 , r , a k ) \sum_{r=k}^n cnt(1,r,a_k) ∑r=kncnt(1,r,ak)似乎很坑……
别慌别慌,这个东西既然很难在线算(即, k k k从 1 1 1慢慢增长到 n n n,轮流计算里面的式子),那就离线算呗。
我们维护一个数组 p p p,其中 p i = c n t ( 1 , i , a k ) p_i=cnt(1,i,a_k) pi=cnt(1,i,ak)。我们将 a a a序列从小到大排序。假设我们目前看到的这个数在原序列的位置是 x x x,我们就将 [ x , n ] [x,n] [x,n]这段区间中的每个数都加上一个 1 1 1,表示在这个区间中的一个 i i i对应的 p i p_i pi增加了 1 1 1。查询的时候,直接求出 ∑ i = x n p i \sum_{i=x}^n p_i ∑i=xnpi即可。注意先查询后修改。
这里涉及到区间查询与区间修改,我们可以使用线段树来快速维护。
Part 2
同Part 1,略
Part 3
∑ l = 1 n ∑ r = l n ∑ k = l r a k \sum_{l=1}^n \sum_{r=l}^n \sum_{k=l}^r a_k ∑l=1n∑r=ln∑k=lrak
∑ k = 1 n a k k ( n − k + 1 ) \sum_{k=1}^n a_k\ k\ (n-k+1) ∑k=1nak k (n−k+1)
这个式子用到了求贡献的套路,然后就可以 O ( n ) O(n) O(n)算了。
综上所述,我们使用线段树来求出了第一、第二部分的式子,使用贡献的套路求出了第三部分的式子,最后求出 a n s 1 − a n s 2 + a n s 3 ans1-ans2+ans3 ans1−ans2+ans3即可。注意取模,并且略微卡常。
总时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn)。本题得到完美解决。
Code
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int mod=1e9+7;
int read()
{
int s=0,w=1;char ch=getchar();
while (ch<'0'||ch>'9')
{
if (ch=='-') w=-w;
ch=getchar();
}
while (ch>='0'&&ch<='9')
{
s=(s<<1)+(s<<3)+(ch^'0');
ch=getchar();
}
return s*w;
}
int n,ans1=0,ans2=0,ans3=0,ans;
int a[500005],tree[2000005],tag[2000005];
struct node
{
int rt,num;
}b[500005];
bool cmp(node x,node y)
{
return x.num<y.num;
}
void pushup(int rt){tree[rt]=(tree[2*rt]+tree[2*rt+1])%mod;}
void f(int l,int r,int rt,int k)
{
tree[rt]=tree[rt]+(r-l+1)*k;
tag[rt]=(tag[rt]+k);
}
void pushdown(int l,int r,int rt)
{
int mid=(l+r)>>1;
f(l,mid,2*rt,tag[rt]);
f(mid+1,r,2*rt+1,tag[rt]);
tag[rt]=0;
}
void change(int nl,int nr,int l,int r,int rt,int k)
{
if (l!=r) pushdown(l,r,rt);
if (nl<=l&&r<=nr)
{
f(l,r,rt,k);
return;
}
int mid=(l+r)>>1;
if (nl<=mid) change(nl,nr,l,mid,2*rt,k);
if (nr>mid) change(nl,nr,mid+1,r,2*rt+1,k);
pushup(rt);
}
int query(int nl,int nr,int l,int r,int rt)
{
if (nl>nr||nl<=0||nr<=0||nl>n||nr>n) return 0;
if (l!=r) pushdown(l,r,rt);
if (nl<=l&&r<=nr) return tree[rt];
int mid=(l+r)>>1,sumv=0;
if (nl<=mid) sumv=query(nl,nr,l,mid,2*rt);
if (nr>mid) sumv=sumv+query(nl,nr,mid+1,r,2*rt+1);
return sumv;
}
signed main()
{
cin>>n;
for (int i=1;i<=n;i++) a[i]=read();
for (int i=1;i<=n;i++) b[i].rt=i,b[i].num=a[i];
sort(b+1,b+n+1,cmp);
for (int i=1;i<=n;i++)
{
int now=(a[b[i].rt]*(b[i].rt))%mod;
now=(now*(query(b[i].rt,n,1,n,1)%mod))%mod;
ans1=(ans1+now)%mod;
change(b[i].rt,n,1,n,1,1);
}
for (int i=1;i<=4*n;i++) tree[i]=tag[i]=0;
for (int i=1;i<=n;i++)
{
int now=(a[b[i].rt]*(n-b[i].rt+1))%mod;
now=(now*(query(1,b[i].rt-1,1,n,1)%mod))%mod;
ans2=(ans2+now)%mod;
change(b[i].rt,n,1,n,1,1);
}
for (int i=1;i<=n;i++)
{
int now=(i*(n-i+1))%mod;
now=(now*a[i])%mod;
ans3=(ans3+now)%mod;
}
ans=((ans1-ans2+ans3)%mod+mod)%mod;
cout<<ans<<endl;
return 0;
}
Summary
这题事实上,每走一步都是套路。
①你不把式子写下来做什么题。
②这个式子看得很难受?推一波。
③前缀和,把这个式子拆成三个部分。
④在线难搞?就离线呗。
⑤区间修改,区间查询直接用线段树。
CF ×2300的一道好题,考察了数据结构(线段树)、数学能力(推式子水平)与前缀和,还特别考察了耐心。你不能把式子推个几步就去打代码,这样可能会出现调试时间过长甚至算法假掉的情况,一定要推出式子之后,带至少两组数据进去,打代码的时候对照着式子模拟,样例过了还要对拍。线段树常数很大,还要卡卡常。