【题解】 luogu p3674小清新人渣的本愿
[题目链接](https://www.luogu.com.cn/problem/P3674)
算法:bitset+莫队
先把询问都离线下来,用莫队判断每个询问区间。并维护两个bitset \(s_1,s_2\),一个判断 \(a_i\)是否在当前区间内。若&a_i&在bitset1中位上的值为true,那\(n-a_i\)在bitset2中那一位上也为true。
关于操作一,其实就是询问 是否有两个数\(a,b\)在区间内,满足\(a+x=b\)。那么我们只需要把\(s1\)左移x位并&上自己,看结果是否不为0就好了,因为若不为0,说明有一个数\(a+x\)与\(a\)同时在区间内。
关于操作二,考虑把它转换成操作一,若存在两个数\(a_i,b_i\),使\(a_i+b_i=x\),设\(n-a_i\)为\(o\),那么\(n-o+b_i=x\),则\(o-b_i=n-x\),这样就转换成了操作一,把\(s_2\)左移\(n-x\)位然后再&上\(s_1\)看结果就好了
操作三不好用bitset做,就直接暴力枚举约数暴搞就好了,说实话这个操作和这题有点格格不入。
记得弄个桶判一下每个元素个数,这个题有重复元素。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <bitset>
#define maxn 1000005
int n,m,a[maxn],bar[maxn],len,ans[maxn],ton[maxn];
std::bitset <maxn> s1,s2,k;
struct question{
int l,r,opt,x,whi;
}op[maxn];
bool cmp(question q,question p) {
if (bar[q.l] == bar[p.l]) return q.r < p.r;
else return bar[q.l] < bar[p.l];
}
void mo() {
int ll = 1,rr = 0;
for (int i = 1;i <= m;i++) {
while (rr < op[i].r) {
rr++;
if ((++ton[a[rr]]) > 0)s1.set(a[rr]),s2.set(n - a[rr]);
}
while (ll > op[i].l) {
ll--;
if ((++ton[a[ll]]) > 0)s1.set(a[ll]),s2.set(n - a[ll]);
}
while (rr > op[i].r) {
if ((--ton[a[rr]]) <= 0)s1.set(a[rr],0),s2.set(n - a[rr],0);
rr--;
}
while (ll < op[i].l) {
if (!(--ton[a[ll]]))s1.set(a[ll],0),s2.set(n - a[ll],0);
ll++;
}
if (op[i].opt == 1) {
k = (s1 & (s1 << op[i].x));
if (k.any()) ans[op[i].whi] = 1;
}
else if (op[i].opt == 2) {
k = (s1 & (s2 >> (n - op[i].x)));
if (k.any()) ans[op[i].whi] = 1;
}
else {
for (int j = 1;j <= sqrt(op[i].x);j++) {
if (op[i].x % j == 0 && s1[j] && s1[op[i].x / j]) ans[op[i].whi] = 1;
}
}
}
}
int main() {
scanf("%d%d",&n,&m);
for (int i = 1;i <= n;i++) {
scanf("%d",&a[i]);
}
for (int i = 1;i <= m;i++)
scanf("%d%d%d%d",&op[i].opt,&op[i].l,&op[i].r,&op[i].x),op[i].whi = i;
len = sqrt(n);
for (int i = 1;i <= n;i++) {
bar[i] = i / len + 1;
}
std::sort(op + 1,op + m + 1,cmp);
mo();
for (int i = 1;i <= m;i++) {
if (ans[i]) printf("hana\n");
else printf("bi\n");
}
return 0;
}