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

时间:2022-01-24
本文章向大家介绍【洛谷】P5355 [Ynoi2017] 由乃的玉米田(莫队),主要包括【洛谷】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<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<vector>
#include<bitset>
using namespace std;
const int N=1e5+10;
const int K=320;
bitset<N> 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];
vector<query>q1[N];
bool cmp(query a,query b){int i=a.l/len,j=b.l/len; return i!=j?i<j:a.r<b.r;}
void add(int x){cnt[x]++;if(cnt[x]==1) s1[x]=1,s2[N-x]=1;}
void del(int x){cnt[x]--;if(cnt[x]==0) s1[x]=0,s2[N-x]=0;}
int main()
{
	scanf("%d%d",&n,&m); len=n/sqrt(m);for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	for(int opt,l,r,x,i=1;i<=m;i++)
	{
		scanf("%d%d%d%d",&opt,&l,&r,&x);
		if(opt!=4||x>K) 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]<N) now=max(now,las[a[j]*i]);
			if(a[j]%i==0) now=max(now,las[a[j]/i]);
			lat[j]=now;
		}
		for(int j=0;j<q1[i].size();j++)
		    ans[q1[i][j].id]=lat[q1[i][j].r]>=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(i<r) add(a[++i]);
		while(i>r) del(a[i--]);
		while(j>l) add(a[--j]);
		while(j<l) del(a[j++]);
		if(q[k].opt==1) ans[id]=(s1&(s1<<x)).any();
		else if(q[k].opt==2) ans[id]=(s1&(s2>>(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<N;t++)
			    if(s1[t]&&s1[t*x]) {ans[id]=true;break;}
		}
	}
	for(int i=1;i<=m;i++) puts(!ans[i]?"yumi":"yuno");
	return 0;
}

原文地址:https://www.cnblogs.com/NLCAKIOI/p/15840861.html