loj 2254 bzoj 5016 「SNOI2017」一个简单的询问

Description

给你一个长度为N的序列ai,1≤i≤N和q组询问,每组询问读入l1,r1,l2,r2,需输出
get(l,r,x)表示计算区间[l,r]中,数字x出现了多少次。

        先纪念第100篇博客啦啦啦~

        第一步想到的是肯定可以通过某种方式,将多个区间的乘法消掉,改成加法或者单个区间的问题。我第一次推的时候把get1看成a[i],get2看成b[i],然后通过莫队求出Σa[i],Σa[i]^2,Σb[i],Σb[i]^2,推了一个小时都没有推出来,后来换成下面的方法就可以了。

        由于get(l1,r1)可以变成get(1,r1)-get(1,l1-1),所以我们可以把式子转换为get(1,r1)-get(1,l1-1)*get(1,r2)-get(1,l2-1),整理一下得到get(1,r1)*get(1,r2)-get(1,r1)*get(1,l2-1)-get(1,l1-1)*get(1,r2)+get(1,l1-1)*get(1,l2-1),这样就之和区间的前缀和有关系了。

       所以我们将每个询问拆成以上四组区间,用cntl[x]表示乘号左边区间内每个数字x出现的次数,用cntr[x]表示乘号右边区间内每个数字x出现的次数,将所有询问离线用莫队处理,由于添加一个数字,可以o(1)求出当前答案的变化量,如左边添加了一个x,那么当前cntl[x]*cntr[x]就变成了(cntl[x]+1)*(cntr[x]),即答案多了cntr[x],所以我们通过莫队离线处理每个区间,最后一并输出即可。

      下附AC代码。

#include<iostream>
#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<math.h>
#define maxn 50005
using namespace std;
typedef long long ll;
struct nod
{
	int l,r,flag,id;
	nod(int a,int b,int c,int d)
	{
		l=a;r=b;flag=c;id=d;
	}
	nod(){}
}query[maxn<<2];
int n,q,m;
int a[maxn];
int bel[maxn];
ll cntl[maxn],cntr[maxn];
ll ans[maxn];
bool operator<(nod a,nod b)
{
	return bel[a.l]!=bel[b.l] ? bel[a.l]<bel[b.l] : a.r<b.r;
}
int main()
{
	scanf("%d",&n);m=sqrt(n);bel[0]=1;
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
		bel[i]=i/m+1;
	}
	scanf("%d",&q);
	for(int i=1;i<=q;i++)
	{
		int l1,r1,l2,r2;
		scanf("%d%d%d%d",&l1,&r1,&l2,&r2);
		query[i]=nod(min(r1,r2),max(r1,r2),1,i);
		query[i+q]=nod(min(r1,l2-1),max(r1,l2-1),-1,i);
		query[i+q+q]=nod(min(l1-1,r2),max(l1-1,r2),-1,i);
		query[i+q+q+q]=nod(min(l1-1,l2-1),max(l1-1,l2-1),1,i);
	}
	q=q<<2;
	sort(query+1,query+1+q);
	int nl=0,nr=0;ll res=0;
	for(int i=1;i<=q;i++)
	{
		int l=query[i].l,r=query[i].r;
		while(nr<r) nr++,cntr[a[nr]]++,res+=cntl[a[nr]];
		while(nl<l) nl++,cntl[a[nl]]++,res+=cntr[a[nl]];
		while(nr>r) res-=cntl[a[nr]],cntr[a[nr]]--,nr--;
		while(nl>l) res-=cntr[a[nl]],cntl[a[nl]]--,nl--;
		ans[query[i].id]+=(((ll)query[i].flag)*res);
	}
	q>>=2;
	for(int i=1;i<=q;i++)
	{
		printf("%lld\n",ans[i]);
	}
} 



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值