P3674 小清新人渣的本愿 (莫队 + bitset)

题目链接: P3674 小清新人渣的本愿

大致题意

给定一个长度为 n n n的序列, 有 m m m次询问操作.

1 l r x 表示询问 [ l , r ] [l, r] [l,r]是否存在 a − b = x a - b = x ab=x ( a , b a, b a,b可以是同一位置的数字)
2 l r x 表示询问 [ l , r ] [l, r] [l,r]是否存在 a + b = x a + b = x a+b=x ( a , b a, b a,b可以是同一位置的数字)
3 l r x 表示询问 [ l , r ] [l, r] [l,r]是否存在 a × b = x a \times b = x a×b=x ( a , b a, b a,b可以是同一位置的数字)

解题思路

思维

我们首先考虑如何在一个序列上求出是否存在两个数字 a , b a, b a,b, 使得 a − b = x a - b = x ab=x. (多次询问)

比较理想的做法可能就是把序列排序后, 每次用双指针进行扫描了.
或者数据值域像本题一样不是很大, 我们可以直接开个去记录所有数值是否出现.

但无论哪种做法, 我们单次查询的复杂度都是 O ( n ) O(n) O(n)的.


考虑优化:

如果我们用01序列来表示每个数值是否出现过(下标从左向右依次递增, 当前位置若为 1 1 1, 则表示该数值出现过).
变形等式: a = b + x a = b + x a=b+x. 则询问等价于: 对于序列中每一个出现过的数值而言, 往后数 x x x个数, 问该位置的数值是否出现过.

我们发现这个问题可以转化为一个求交集的问题. 即: 设当前的 01 01 01序列 A A A, 把 A A A中的所有数值向后移动 x x x位得到序列 B B B, 然后对于序列 A , B A, B A,B, 我们把每个对应位置上的数值进行与运算得到序列 C C C, 如果最终序列 C C C中存在 1 1 1, 则表明存在 a − b = x a - b = x ab=x.

我们发现可以通过bitset来进行优化, 优化后单次查询复杂度为 O ( n w ) O(\frac{n}{w}) O(wn).


莫队

由于本题是询问的区间上是否存在 a , b a, b a,b, 因此我们不妨考虑通过莫队去维护. 每次在bitset中翻转某一位的复杂度也是 O ( 1 ) O(1) O(1)的, 因此我们可以保证 O ( n ) O(\sqrt{n}) O(n )的修改.

对于操作①: 由上文分析可得, 通过用bitset交集的方式求解. 单次查询复杂度为 O ( n w ) O(\frac{n}{w}) O(wn).

对于操作②: 我们移项可得 a = x − b a = x - b a=xb, 等价于 a = − b + x a = -b + x a=b+x. 由于我们在bitset中并不好维护负值, 因此考虑对于等式两边, 我们同时用值域上界 M A X MAX MAX减去, 得到: M A X − a = M A X + b − x MAX - a = MAX + b - x MAXa=MAX+bx, 等价于: M A X − a = b + ( M A X − x ) MAX - a = b + (MAX - x) MAXa=b+(MAXx).
若令 a ′ = M A X − a , x ′ = M A X − x a' = MAX - a, x' = MAX - x a=MAXa,x=MAXx, 则有: a ′ = b + x ′ a' = b + x' a=b+x. 到此为止, 我们把操作②转化为了类似操作①的形式. 如果我们再用一个
bitset
去维护 M A X − v a l MAX-val MAXval是否出现, 我们即可解决操作②. 单次查询的复杂度同样为 O ( n w ) O(\frac{n}{w}) O(wn).

对于操作③: 我们不妨直接枚举 x x x的约数, 然后查询约数是否存在即可. 单次查询的复杂度为 O ( x ) O(\sqrt{x}) O(x ).


我们发现复杂度的瓶颈是在操作①②上, 最坏复杂度为 O ( n m w ) O(\frac{nm}{w}) O(wnm). 在 w = 64 w = 64 w=64时, 大概是 1.5 × 1 0 8 1.5 \times 10^8 1.5×108.

评测姬那么厉害, 3 s 3s 3s当然可以跑完啦! (洛谷神机!!!)

AC代码

#include <bits/stdc++.h>
#define rep(i, n) for (int i = 1; i <= (n); ++i)
using namespace std;
typedef long long ll;
const int N = 1E5 + 10, B = 320, MAXN = N - 10;
int w[N];
struct mo {
    
    
	int tp, l, r, x, id;
	bool operator< (const mo& t) const {
    
    
		if (l / B != t.l / B) return l < t.l;
		return l / B & 1 ? r < t.r : r > t.r;
	}
}; vector<mo> area;


bool res[N];
bitset<N> vis1, vis2; int cou[N];
void add(int c) {
    
     if (++cou[c] == 1) vis1[c] = 1, vis2[MAXN - c] = 1; }
void sub(int c) {
    
     if (--cou[c] == 0) vis1[c] = 0, vis2[MAXN - c] = 0; }

bool ask1(int x) {
    
     return (vis1 & (vis1 >> x)).any(); }
bool ask2(int x) {
    
     return (vis1 & (vis2 >> x)).any(); }
bool ask3(int x) {
    
    
	for (int i = 1; i <= x / i; ++i) {
    
    
		if (x % i == 0 and vis1[i] and vis1[x / i]) return 1;
	}
	return 0;
}
int main()
{
    
    
	int n, m; cin >> n >> m;
	rep(i, n) scanf("%d", &w[i]);

	rep(i, m) {
    
    
		int tp, l, r, x; scanf("%d %d %d %d", &tp, &l, &r, &x);
		area.push_back({
    
     tp, l, r, x, i });
	}
	sort(area.begin(), area.end());

	int L = 1, R = 0;
	for (auto& [tp, l, r, x, id] : area) {
    
    
		while (l < L) add(w[--L]);
		while (r > R) add(w[++R]);
		while (L < l) sub(w[L++]);
		while (R > r) sub(w[R--]);

		if (tp == 1) res[id] = ask1(x);
		else if (tp == 2) res[id] = ask2(MAXN - x);
		else res[id] = ask3(x);
	}

	rep(i, m) puts(res[i] ? "hana" : "bi");

	return 0;
}

END

猜你喜欢

转载自blog.csdn.net/weixin_45799835/article/details/121282307