洛谷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;
}
posted @ 2019-05-18 08:41  宇興  阅读(164)  评论(0编辑  收藏  举报