LOJ2254 && bzoj5016 [Snoi2017]一个简单的询问 莫队算法

题目链接:LOJ  bzoj

5016: [Snoi2017]一个简单的询问

Time Limit: 30 Sec   Memory Limit: 512 MB
Submit: 132   Solved: 102
[ Submit][ Status][ Discuss]

Description

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

Input

第一行,一个数字N,表示序列长度。
第二行,N个数字,表示a1~aN
第三行,一个数字Q,表示询问个数。
第4~Q+3行,每行四个数字l1,r1,l2,r2,表示询问。
N,Q≤50000
N1≤ai≤N
1≤l1≤r1≤N
1≤l2≤r2≤N
注意:答案有可能超过int的最大值

Output

对于每组询问,输出一行一个数字,表示答案

Sample Input

5
1 1 1 1 1
2
1 2 3 4
1 1 4 4

Sample Output

4
1
题解:莫队算法。

因为是询问一个区间内一个数的出现次数,满足前缀和性质,所以将问题转化为前缀和求解。

但每组询问有两个区间,不方便直接使用莫队求解。

对于每个x,定义sum[i]为1~i里x的出现数量,那么对于一组询问即转化为求(sum[r1]-sum[l1-1])*(sum[r2]-sum[l2-1])的值。

将上式展开得sum[r1]*sum[r2]-sum[l1-1]*sum[r2]-sum[r1]*sum[l2-1]+sum[l1-1]*sum[l2-1]。

由此可以定义一个区间[l,r]的价值为sum[l]*sum[r],将每组询问拆分为四个区间:[r1,r2],[l1-1,l2-1],[l1-1,r2],[r1,l2-1],其中第一、二个区间为正贡献,第三、四个区间为负贡献,至此可以使用莫队求解。

代码:

#include<bits/stdc++.h>
#define maxn 50005
using namespace std;
typedef long long LL;
int read()
{
	char c;int sum=0,f=1;c=getchar();
	while(c<'0' || c>'9'){if(c=='-')f=-1;c=getchar();}
	while(c>='0' && c<='9'){sum=sum*10+c-'0';c=getchar();}
	return sum*f;
}
int n,q,l1,l2,r1,r2;
int a[maxn],bel[maxn],cntl[maxn],cntr[maxn];
LL ans[maxn];
struct node{
	int l,r,flag,id;
	node(int a,int b,int c,int d)
	{
		l=a;r=b;flag=c;id=d;
	}
	node(){}
}query[maxn<<2];
bool operator<(node a,node b)
{
	return bel[a.l]!=bel[b.l]?bel[a.l]<bel[b.l]:a.r<b.r;
}
int main()
{
	n=read();
	int m=sqrt(n);
	for(int i=1;i<=n;i++)
	{
		a[i]=read();
		bel[i]=i/m+1;
	}
	q=read();
	for(int i=1;i<=q;i++)
	{
		l1=read(),r1=read(),l2=read(),r2=read();
		query[i]=node(min(r1,r2),max(r1,r2),1,i);
		query[i+q]=node(min(r1,l2-1),max(r1,l2-1),-1,i);
		query[i+q+q]=node(min(l1-1,r2),max(l1-1,r2),-1,i);
		query[i+q+q+q]=node(min(l1-1,l2-1),max(l1-1,l2-1),1,i);
	}
	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]); 
	return 0;
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值