洛谷P3674 小清新人渣的本愿
题意:
给定序列a,长度为n,有m次操作,opt表示该次操作类型,l,r表示操作的区间,x表示这次操作的x
-
操作1:询问一个区间,是否可以选出两个数,满足它们的差为x。
-
操作2:询问一个区间,是否可以选出两个数,满足它们的和为x。
-
操作3:询问一个区间,是否可以选出两个数,满足它们的乘积为x。
选出的这两个数可以是同一个位置的数。
定义c为每次的x和ai中的最大值,ai >= 0,每次的x>=2。
n,m,c <= 100000
前置技能:
-
普通莫队算法
-
bitset基础
-
状态压缩/位运算基础
思路:对于此类离线的序列问题,想到莫队算法,时间复杂度为\(O(n\sqrt n*(每次转移复杂度))\)。
n为1e5,若每次转移复杂度较低,则总时间复杂度可以接受。
难点在于转移和解的判定。对于每个操作,若暴力转移,可以用3个数组,分别记录当前区间的和、差、积,在移动当前区间端点时,线性的扫一遍区间,更新这三个数组。这样每次转移的复杂度为\(O(n)\) ,总时间复杂度为\(O(n^2\sqrt n)\) ,显然会TLE。
于是需要更优的方法来判定答案并以较低复杂度转移。实际上,可以在值域上维护一个集合,表示当前区间内存在的数的种类。对于每种操作,可以通过某种快速的判定方法判断集合内元素是否满足要求。
考虑二进制状态压缩,因为n比较大,可以用bitset维护集合,记作s1。
bitset基础技能
因为s1建在值域上,因此,对于一个数A,A+X在bitset的位置为\(s1[A<<X]\),A-X在bitset的位置为 \(s1[A>>X]\) 。
s1.any()函数返回s1中是否存在1。
-
区间端点转移:按照普通莫队的套路,记录一个cnt数组。
-
增长区间时,把bitset的对应位修改为1,++cnt[v[p]]。
-
缩短区间时,--cnt[v[p]],若cnt[v[p]]==0,把bitset对应位修改为0。
这样,每次可以\(O(1)\)转移。
-
-
查询答案:
-
操作1:若存在a、b,使a-b=x,则b=a-x。因此该操作等价于在对应区间内是否同时存在a和a-x这两个数,即 (s1&(s1>>x)).any() 。又a=b+x,因此该操作也等价于在对应区间内是否同时存在b和b+x这两个数。所以写成 (s1&(s1<<x)).any() 也是可以的。
-
操作2:若存在a、b,使a+b=x,则b=x-a。因此该操作等价于在对应区间内是否同时存在a和x-a这两个数。只用s1无法表示-a,为了表示出-a ,考虑建一个新的bitset s2,因为下标不支持负数,所以s2的第i位表示N-i,N为值域上限。因此,x-a可以表示为(N-a)+(x-N),相当于s2左移(x-N)位。若两个bitset的对应位均为1,即 s1 & ( s2<<(x-N) ).any()则存在合法解。
但是,这样写是有问题的,因为\((x-N)<0\),左移的位数不能是个负数。所以,要写成右移(N-x)位,相当于(N-a)-(N-x),即s1 & ( s2>>(N-x) )。
-
操作3:因为x范围有限,直接\(O(\sqrt x)\)枚举x的每对因数在s1中查询是否存在即可。
-
Code
#include <bits/stdc++.h>
using namespace std;
const int N = 100005;
bitset<N> s1, s2, res;//可以再用一个bitset代替数组存储每个询问的答案 节省空间
int n, m, len, v[N], cnt[N];
struct Q {
int l, r, id, block, opt, x;
} q[N];
inline bool cmp(const Q &a, const Q &b) {
return (a.block ^ b.block) ? a.block < b.block : (a.block & 1) ? a.r < b.r : a.r >b.r ;//奇偶性玄学排序优化
}
inline void add(int p) {
if(!cnt[v[p]]++) s1[v[p]] = s2[N - v[p]] = 1;
}
inline void del(int p) {
if(!--cnt[v[p]]) s1[v[p]] = s2[N - v[p]] = 0;
}
int main() {
scanf("%d%d", &n, &m);
len = ceil(pow(n * 1.0, 0.5));//分块大小
for(int i = 1; i <= n; ++i) scanf("%d", &v[i]);
for(int i = 1; i <= m; ++i) {
scanf("%d%d%d%d", &q[i].opt , &q[i].l , &q[i].r , &q[i].x);
q[i].block = (q[i].l - 1) / len + 1;//左端点所在块
q[i].id = i;
}
sort(q + 1, q + 1 + m, cmp);
int l = q[1].l , r = l - 1, ql, qr, opt, qx;
for(int i = 1; i <= m; ++i) {
ql = q[i].l;
qr = q[i].r;
opt = q[i].opt;
qx = q[i].x;
while(l < ql) del(l++);
while(l > ql) add(--l);
while(r < qr) add(++r);
while(r > qr) del(r--);
if(opt == 1) res[q[i].id] = (s1 & (s1 << qx)).any();
else if(opt == 2) res[q[i].id] = (s1 & (s2 >> (N - qx))).any();
else for(int j = 1; j * j <= qx; ++j) if(!(qx % j) && s1[j] && s1[qx / j]) res[q[i].id] = true;
}
for(int i = 1; i <= m; ++i) (res[i]) ? puts("hana") : puts("bi");
return 0;
}