「BZOJ 4810」由乃的玉米田

Description

题目链接:BZOJ 4810

给你一个长度为 $n$ 的序列 $a_i$,有 $m$ 次操作,操作分为以下 $3$ 种:

  • 1 l r x:询问能否在区间 $[l,r]$ 内选出两个数使得它们的差为 $x$。
  • 2 l r x:询问能否在区间 $[l,r]$ 内选出两个数使得它们的和为 $x$。
  • 3 l r x:询问能否在区间 $[l,r]$ 内选出两个数使得它们的积为 $x$。

如果可以,输出 yuno,否则输出 yumi

数据范围:$n,m,a_i\le 10^5$


Solution

由于没有修改操作,区间又没法用数据结构维护,我们可以考虑莫队

现在把询问离线下来了,我们要考虑的就是如何利用已知信息求解。注意到 $a_i$ 的范围很小,所以我们可以用 $\text{bitset}$ 来记录每个数是否出现过。

设满足条件的两个数为 $x,y$,给出的值为 $k$,我们对三种操作分类讨论。

操作 1

我们对 $x-y=k$ 变形为 $y=x-k$,那么如果 $x,y$ 存在当且仅当存在 $x$ 和 $x-k$。因此我们可以用一个 $\text{bitset}$(假设为 $f$)维护是否有 $i$ 这个数,判定方法为 $f\ \&\ (f<<x)$。

操作 2

依旧对 $x+y=k$ 进行变形,得到 $y=-x+k$。但是注意到此处等式右边的 $x$ 为负,没法直接用 $\text{bitset}$ 维护。所以我们令 $x’=N-x$($N$ 为 $a_i$ 的最大值),那么我们用另一个 $\text{bitset}$(假设为 $g$)维护是否有 $N-i$ 这个数,那么等式转化为 $y=N-(-x+k)=N+x-k$ 即 $f$ 中有 $x$,$g$ 中有 $N+x-k$,判定方法为 $f\ \&\ (g>>(N-k))$。

操作 3

如果直接枚举因子,复杂度为 $O(n\sqrt n)$,但是莫队的复杂度也是 $O(n\sqrt n)$,因此不会带来额外的复杂度。所以操作 $3$ 中只要大力枚举 $k$ 的因子,在 $f$ 中查找即可。判定方法为 $f[d]\ \text{AND}\ f[\frac{k}{d}]$(其中 $k\bmod d=0$)。

时间复杂度:$O(n\sqrt n)$


Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <bitset>

const int N=1e5;
int n,m,a[N+5],b[N+5],cnt[N+5];
bool ans[N+5];
std::bitset<N+5> f1,f2;
struct ques {
int opt,l,r,k,id;
bool operator < (const ques &rhs) const {
return b[l]==b[rhs.l]?r<rhs.r:l<rhs.l;
}
}q[N+5];

void add(int x) {
if(++cnt[x]==1) f1[x]=f2[N-x]=1;
}
void del(int x) {
if(--cnt[x]==0) f1[x]=f2[N-x]=0;
}
int main() {
scanf("%d%d",&n,&m);
int len=sqrt(n);
for(int i=1;i<=n;++i) scanf("%d",&a[i]),b[i]=(i-1)/len+1;
for(int i=1;i<=m;++i) scanf("%d%d%d%d",&q[i].opt,&q[i].l,&q[i].r,&q[i].k),q[i].id=i;
std::sort(q+1,q+m+1);
for(int l=1,r=0,i=1;i<=m;++i) {
int x=q[i].l,y=q[i].r,k=q[i].k;
while(x<l) add(a[--l]);
while(x>l) del(a[l++]);
while(y<r) del(a[r--]);
while(y>r) add(a[++r]);
if(q[i].opt==1) {
ans[q[i].id]=(f1&(f1<<k)).any();
} else if(q[i].opt==2) {
ans[q[i].id]=(f1&(f2>>(N-k))).any();
} else {
for(int d=1;d*d<=k;++d) if(k%d==0&&f1[d]&&f1[k/d]) {ans[q[i].id]=1;break;}
}
}
for(int i=1;i<=m;++i) puts(ans[i]?"yuno":"yumi");
return 0;
}
0%