洛谷P3674 小清新人渣的本愿【莫队+bitset】

时空限制 3000ms / 128MB

题目描述

这个题是这样的:
给你一个序列a,长度为n,有m次操作,每次询问一个区间是否可以选出两个数它们的差为x,或者询问一个区间是否可以选出两个数它们的和为x,或者询问一个区间是否可以选出两个数它们的乘积为x ,这三个操作分别为操作1,2,3
选出的这两个数可以是同一个位置的数

输入格式:

第一行两个数n,m
后面一行n个数表示ai
后面m行每行四个数opt l r x
opt表示这个是第几种操作,l,r表示操作的区间,x表示这次操作的x

输出格式:

对于每个询问,如果可以,输出hana,否则输出bi

说明

定义c为每次的x和ai中的最大值,ai >= 0,每次的x>=2
对于100%的数据,n,m,c <= 100000


题目分析

题目没有要求强制在线,很快联想到了莫队
但说实话要是没有偷偷瞄一眼题解还真的没想到bitset这种黑科技

此题需要两个 b i t s e t bitset bitset
r e m 1 rem1 rem1记录每个数字的出现情况,若 r e m [ i ] = = 1 rem[i]==1 rem[i]==1,则代表当前区间存在数字 i i i
那么对于是否存在 a − b = x a-b=x ab=x的询问,只需要检查 r e m 1 &amp; ( r e m 1 &lt; &lt; x ) rem1\&amp;(rem1&lt;&lt;x) rem1&(rem1<<x)中是否存在1即可

b i t s e t bitset bitset有一个成员函数 a n y ( ) any() any()可以返回bitset中是否存在某一位是1,直接调用即可

对于是否存在 a + b = x a+b=x a+b=x的询问,还是需要一个bitset
r e m 2 rem2 rem2记录数字 N − x N-x Nx在当前区间是否存在(N可以是大于题目给定值域范围最大值的任何数)
r e m 2 [ i ] = = 1 rem2[i]==1 rem2[i]==1代表当前区间存在 N − i N-i Ni

为方便表示记 r e m 2 rem2 rem2 i i i位为 i ′ i^{&#x27;} i
那么 a + b = x a+b=x a+b=x可以转化为 N − b ′ + a = x ⇒ b ′ − a = N − x N-b^{&#x27;}+a=x\Rightarrow b^{&#x27;}-a=N-x Nb+a=xba=Nx
所以只需要检查 r e m 2 &amp; ( r e m 1 &lt; &lt; ( N − x ) ) rem2\&amp;(rem1&lt;&lt;(N-x)) rem2&(rem1<<(Nx))中是否存在1即可

对于是否存在 a ∗ b = x a*b=x ab=x的询问
直接枚举 x x x的约数,复杂度为 O ( x ) O(\sqrt{x}) O(x ),完全可行


#include<iostream>
#include<cstdio>
#include<cmath>
#include<queue>
#include<algorithm>
#include<cstring>
#include<bitset>
using namespace std;
typedef long long lt;

int read()
{
    int f=1,x=0;
    char ss=getchar();
    while(ss<'0'||ss>'9'){if(ss=='-')f=-1;ss=getchar();}
    while(ss>='0'&&ss<='9'){x=x*10+ss-'0';ss=getchar();}
    return x*f;
}

const int maxn=200010;
int n,m,t;
int a[maxn],ans[maxn];
int cnt[maxn],L=1,R=0;
bitset<maxn> rem1,rem2;
struct node{int opt,ll,rr,k,id;}q[maxn];
bool cmp(node a,node b){ return (a.ll/t)==(b.ll/t)?a.rr<b.rr:a.ll/t<b.ll/t;}

void add(int x,int k)
{
	cnt[x]++;
	if(cnt[x]==1) 
	rem1[x]=1,rem2[maxn-x]=1;
}

void del(int x,int k)
{
	cnt[x]--;
	if(cnt[x]==0) 
	rem1[x]=0,rem2[maxn-x]=0;
}

int main()
{
	n=read();m=read(); 
	for(int i=1;i<=n;++i) a[i]=read();
	for(int i=1;i<=m;++i)
	{
		q[i].id=i; q[i].opt=read();
		q[i].ll=read(); q[i].rr=read();
		q[i].k=read();
	}
	
	t=sqrt(n);
	sort(q+1,q+1+m,cmp);
	for(int i=1;i<=m;++i)
	{
		int judge=0;
		while(R<q[i].rr) add(a[++R],q[i].k);
		while(R>q[i].rr) del(a[R--],q[i].k);
		while(L<q[i].ll) del(a[L++],q[i].k);
		while(L>q[i].ll) add(a[--L],q[i].k);
		
		if(q[i].opt==1&&(rem1&(rem1<<q[i].k)).any()) judge=1;
		else if(q[i].opt==2&&((rem1<<(-q[i].k+maxn))&rem2).any()) judge=1;
		else if(q[i].opt==3)
		{
			for(int j=1;j*j<=q[i].k;++j)
			if(q[i].k%j==0){
				if(cnt[j]&&cnt[q[i].k/j]){ judge=1; break;}
			}
		}
		
		if(judge) ans[q[i].id]=1;
		else ans[q[i].id]=0;
	}
	
	for(int i=1;i<=m;++i)
	if(ans[i]) printf("hana\n");
	else printf("bi\n"); 
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值