赞
踩
这题解不是口胡的。
一个月前看这题,早上看了,准备在学校中自习写完作业想一会儿,结果推了一整个中自习加上一个课件加上一堂课都没推出来……想得非常复杂,在那里写了个多项式然后什么NTT,FFT都搞出来了,结果最后复杂度与暴力没什么差别……自闭了……
不推式子怎么行?
∑ 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]
于是这个式子就被拆成了三个部分,我们一个部分一个部分地解决。
= ∑ 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 1,略
∑ 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)。本题得到完美解决。
#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; }
这题事实上,每走一步都是套路。
①你不把式子写下来做什么题。
②这个式子看得很难受?推一波。
③前缀和,把这个式子拆成三个部分。
④在线难搞?就离线呗。
⑤区间修改,区间查询直接用线段树。
CF ×2300的一道好题,考察了数据结构(线段树)、数学能力(推式子水平)与前缀和,还特别考察了耐心。你不能把式子推个几步就去打代码,这样可能会出现调试时间过长甚至算法假掉的情况,一定要推出式子之后,带至少两组数据进去,打代码的时候对照着式子模拟,样例过了还要对拍。线段树常数很大,还要卡卡常。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。