【洛谷】P5355 [Ynoi2017] 由乃的玉米田(莫队)


原题链接

题意

给定一个长度为 \(n\) 的序列,有 \(m\) 次询问,每次询问给出一个区间 \([l,r]\) 和一个 \(x\),问区间内是否存在两个数(可以相同),使得它们的和或差或积或商等于 \(x\)

数据范围

所有输入的数在 \([0,10^5]\) 内,序列中的元素在 \([0,10^5]\) 内。

思路

对于询问和、差、积的操作,直接按照 P3674 的做法求解即可。这里主要讨论一下如何解决询问商的操作。

若想在给定区间中找到一对 \(a,b\),使得 \(a \div b = x\) 。最朴素的想法就是从 \(1\)\(10^5\) 枚举 \(i\),判断 \(i\) 以及 \(ix\) 是否在区间中。注意到序列中的元素在 \([0,10^5]\) 内。可以发现如果 \(ix > 10^5\) 就没有必要继续枚举了,也就是说,随着 \(x\) 的增大,枚举 \(i\) 的次数会越来越少,这就启示我们可以采用根号分治的办法解决这个问题。

首先,对于 \(x \geq \sqrt{N}\) 的部分(\(N\) 表示序列元素的最大值,下同),直接暴力枚举 \(i\)。单次枚举次数不会超过 \(\sqrt N\),那么总的时间复杂度也就保持在 \(O(n\sqrt N)\),可以接受。

其次,对于 \(x < \sqrt N\) 的部分。可以将 \(x\) 相同的询问一起处理,即对于序列中的每一个 \(a[i]\),预处理出在 \([1,i]\) 中所有满足商为 \(x\) 的一对数中较小坐标的最大值(可以简单理解为最近的一对数,因为要满足两个数都在区间中,所以取较小值),记为 \(late[i]\)。那么对于一个询问区间 \([l,r]\),只要满足 \(late[r] \geq l\),那么就必定有解,反之则无解。由于只会遍历 \([1,\sqrt N]\) 次,每次遍历的复杂度都为 \(O(n)\),总的时间复杂度还是维持在 \(O(n \sqrt N)\) 级别。

最后,需要注意,在预处理时, \(a[j]\) 即可以是除数,也可以是被除数,不要忘了判断 \(a[j]/x\) (当且仅当 \(a[j] \mod x=0\) 时)是否存在。

code:

#include
#include
#include
#include
#include
#include
using namespace std;
const int N=1e5+10;
const int K=320;
bitset s1,s2;
int tot,len,n,m,a[N],cnt[N],las[N],lat[N]; int ans[N];
struct query{int opt,l,r,x,id;}q[N];
vectorq1[N];
bool cmp(query a,query b){int i=a.l/len,j=b.l/len; return i!=j?iK) q[++tot]=query{opt,l,r,x,i};
		else q1[x].push_back(query{opt,l,r,x,i});
	}
	for(int i=1;i<=K;i++)
	{
		if(!q1[i].size()) continue;
		memset(las,0,sizeof(las));memset(lat,0,sizeof(lat));int now=0;
		for(int j=1;j<=n;j++)
		{
			las[a[j]]=j; //别忘了更新每个数最后出现的位置,因为可以是同一个数,所以放在判断前更新 
			if(i*a[j]=q1[i][j].l;
	}
	sort(q+1,q+tot+1,cmp);
	for(int i=0,j=1,k=1;k<=tot;k++)
	{
		int l=q[k].l,r=q[k].r,id=q[k].id,x=q[k].x;
		while(ir) del(a[i--]);
		while(j>l) add(a[--j]);
		while(j>(N-x))).any();
		else if(q[k].opt==3)
		{
			for(int t=1;t*t<=x;t++)
			    if(x%t==0&&(s1[t]&s1[x/t])){ans[id]=true;break;}
		}
		else
		{
			for(int t=1;t*x