当前位置:   article > 正文

CF1167F(Scalar Queries)题解_你要求出 ∑ l=1 n ∑ r=l n f(l,r)

你要求出 ∑ l=1 n ∑ r=l n f(l,r)

这题解不是口胡的。

花絮

一个月前看这题,早上看了,准备在学校中自习写完作业想一会儿,结果推了一整个中自习加上一个课件加上一堂课都没推出来……想得非常复杂,在那里写了个多项式然后什么NTT,FFT都搞出来了,结果最后复杂度与暴力没什么差别……自闭了……

Solution

不推式子怎么行?

∑ l = 1 n ∑ r = l n f ( l , r ) \sum_{l=1}^n \sum_{r=l}^n f(l,r) l=1nr=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=1nr=lnk=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=1nr=lnk=lrak (cnt(1,r,ak)cnt(1,l1,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=1nr=lnk=lrak (cnt(1,r,ak)][l=1nr=lnk=lrak cnt(1,l1,ak)]+[l=1nr=lnk=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=1nr=lnk=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=1nakkr=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=1nr=lnk=lrak

∑ k = 1 n a k   k   ( n − k + 1 ) \sum_{k=1}^n a_k\ k\ (n-k+1) k=1nak k (nk+1)

这个式子用到了求贡献的套路,然后就可以 O ( n ) O(n) O(n)算了。


综上所述,我们使用线段树来求出了第一、第二部分的式子,使用贡献的套路求出了第三部分的式子,最后求出 a n s 1 − a n s 2 + a n s 3 ans1-ans2+ans3 ans1ans2+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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110

Summary

这题事实上,每走一步都是套路

①你不把式子写下来做什么题。
②这个式子看得很难受?推一波。
③前缀和,把这个式子拆成三个部分。
④在线难搞?就离线呗。
⑤区间修改,区间查询直接用线段树。

CF ×2300的一道好题,考察了数据结构(线段树)、数学能力(推式子水平)与前缀和,还特别考察了耐心。你不能把式子推个几步就去打代码,这样可能会出现调试时间过长甚至算法假掉的情况,一定要推出式子之后,带至少两组数据进去,打代码的时候对照着式子模拟,样例过了还要对拍。线段树常数很大,还要卡卡常。

在这里插入图片描述
在这里插入图片描述

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/花生_TL007/article/detail/450673
推荐阅读
相关标签
  

闽ICP备14008679号