CF1167F(Scalar Queries)题解

这题解不是口胡的。

花絮

一个月前看这题,早上看了,准备在学校中自习写完作业想一会儿,结果推了一整个中自习加上一个课件加上一堂课都没推出来……想得非常复杂,在那里写了个多项式然后什么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;
}

Summary

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

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

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

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

1、资源项目源码均已通过严格测试验证,保证能够正常运行;、 2项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行;、 2项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。
1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 、 1资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看READmE.文件(md如有),本项目仅用作交流学习参考,请切勿用于商业用途。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值