zoukankan      html  css  js  c++  java
  • 洛谷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(nsqrt n*(每次转移复杂度)))

    n为1e5,若每次转移复杂度较低,则总时间复杂度可以接受。

    难点在于转移和解的判定。对于每个操作,若暴力转移,可以用3个数组,分别记录当前区间的和、差、积,在移动当前区间端点时,线性的扫一遍区间,更新这三个数组。这样每次转移的复杂度为(O(n)) ,总时间复杂度为(O(n^2sqrt 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;
    }
    
  • 相关阅读:
    JPEG/PNG/GIF图片格式简析
    js-JavaScript常见的创建对象的几种方式
    js-ES6学习笔记-let命令
    js-权威指南学习笔记21
    js-jQuery性能优化(二)
    【读书笔记】iOS-Apple的移动设备硬件
    【读书笔记】iOS-属性中的内存管理参数
    【读书笔记】iOS-自动释放池
    【读书笔记】iOS-分类与协议
    【读书笔记】iOS-动态类型和动态绑定
  • 原文地址:https://www.cnblogs.com/yu-xing/p/10884577.html
Copyright © 2011-2022 走看看