zoukankan      html  css  js  c++  java
  • 学习笔记:莫队

    离线算法。

    算法核心思想是优化暴力。

    一般形式是若干区间询问。

    若能用较少的时间((O(1) - O(log n)))从 ([l, r]) 的答案推演至 ([l, r + 1], [l - 1, r]) 的答案,那么这个问题就可以用莫队优化。

    普通莫队

    设扩展一次的时间为 (O(a))

    ([l, r]) 的答案快速推演至 ([l, r + 1], [l, r - 1], [l + 1, r], [l - 1, r])

    我们考虑把询问排序,对于相邻的询问,([l_i, r_i]) 移动到 ([l_{i+1}, r_{r+1}]),复杂度是 (O(a(|r_i - r_{i-1}| + |l_{i+1}-l_i|)))

    排序方式?考虑分块,把序列分块,设 (i) 所属块为 (pos_i),设块长度为 (S) ,询问排序关键字,先按照左端点块数 (pos_l) 排序,若左端点所属块相等,右端点按从小到大排序。

    复杂度:

    • 右指针:(frac{n^2}{S})
    • 左指针:
      • 块内:每次移动最多不会超过 (S) 次,总共 (mS)
      • 块间:即这一块最后询问到下一块第一个询问,下一个询问左端点一定是大于这个询问左端点的。所以总复杂度不超过 (n)

    平衡算法 (frac{n^2}{S} = mS)(S = frac{n}{sqrt{m}}) 最优。总复杂度 (O(n sqrt{m} imes a))

    模板题:

    带修莫队

    其实就是多了一个时间维。

    ([l, r, t]) 代表区间为 ([l, r]),第 (t) 次修改之后的答案。

    排序:第一关键字为 (pos_l),第二关键字为 (pos_r),第三关键字为 (t)

    (pos_l , pos_r) 相同的一段是一个块。

    设每一块长度为 (S),一共有 (frac{n^2}{S}) 块。

    • (l) 的移动不超过 (mS + n)

    • (r) 的移动不超过 (mS + frac{n^2}{S})

    • (t)(frac{n^2}{S^2}t)

    (n, m) 同阶时,(S = sqrt[3]{nt}) 取到最优解,复杂度 (O(sqrt[3]{n^4t}))

    模板题:

    回滚莫队

    若只能插入,不能删除,可以用回滚莫队。

    排序和普通莫队一样

    (R_i)(i) 这个块最右边点的编号。

    • 暴力求块内的所有区间
    • 同一块左端点,他们所有的跨块询问,这么做:首先强行变成区间 ([R_{pos_l}, R_{pos_l}]) 剩下的询问必然在 (r > R_{pos_l})。转移一个询问让右端点跟着走,然后左端点暴力扩展,暴力撤销。

    复杂度类似普通莫队,左端点每次暴力扩展暴力撤销都在同一个块里,所以复杂度还是跟普通莫队相同的。

    模板题:

    树上莫队

    一般形式是对树上一条简单路径的某些询问。

    考虑把树转化成链,跑欧拉序:

    • dfs 一遍,进入的时候把该节点 push 进序列,退出时再 push 一次。

    (L_x, R_x) 分别为进入 / 退出 (x) 点对应的序列位置。

    这样的序列,我们发现对于一条 (u, v)(设 (L_u < L_v)) 的路上简单路径上的所有点编号:

    • 如果 (lca(u, v) = u),这些编号恰好是 ([L_u, L_v]) 内出现次数为一的编号。
    • 否则, 这些编号恰好是 ([R_u, L_v]) 内出现次数为一的编号 + 一个 (lca(u, v))

    证明可以画画图,感性理解:

    • 如果 (lca(u, v) = u),那么 (u)(v) 的祖先,所以从 (u) 一路 dfs 到 (v) 的第一次,这之中可能会经过其他子树,但肯定会回溯,所以经过其他子树的就肯定出现的两次。
    • 如果 (lca(u, v) ot= u),那么他们不是祖先关系,那么跑 dfs 时,会先进到他们的 LCA,然后到 u,回溯会 LCA,再跑到 v,其中经过别的子树也都出现两次被搞掉了。

    统计是否出现两次一般都是开另外一个数组,看这个编号出现过没有,如果出现过你就加上,已经在了你就删掉。

    模板:

    二次离线莫队

    如果对于 ([l, r]) 快速扩展一步到 ([l, r + 1], [l, r - 1], [l + 1, r], [l - 1, r]) 的操作,不好做,可以把所有扩展的询问离线下来,都查一遍,然后重做,每次用之前弄好的询问,复杂度可以降。

    模板:

    2535. 二次离线莫队

    每次扩展的询问形式是 ([l, r]) 中有多少数可以和 (r+1,r,l,l-1) 匹配。

    这个东西差分成 (sum_{[1, r]} - sum_{[1, l - 1]}) 这样可以扫描线扫一遍。

    至于扫描线,暴力 (n imes C_{14}^7) 是可以过的。

    复杂度 (O(n sqrt{n} + n imes C_{14}^7))

    如果不离线 (sqrt{n})(C_{14}^7) 会叠加起来。

    然后空间就炸了。。

    考虑其一,剩下是对称的,考虑 ([l, r]) 跑到 ([l, R]),其中 (r < R)

    那么差分的左侧都是 ([1, l - 1]) 中所有跟 ([r, R + 1]) 里对称的个数,这样打包到一个结构体了。

    差分的右侧时 ([1, x]) 里和 (x + 1) 匹配的形式,这个东西区间和匹配的数相关,预处理一下就好了。

    #include <iostream>
    #include <cstdio>
    #include <cmath>
    #include <algorithm>
    #include <vector>
    const int N = 100005, L = 14;
    
    typedef long long LL;
    
    using namespace std;
    
    int n, m, K, t, a[N], cnt[1 << L], d[1 << L], f[N], tot, pos[N];
    LL ans[N], res[N];
    
    // R[i] 表示 [1, i] 有多少和 i + 1 匹配的。
    
    int inline get(int x) {
    	int s = 0;
    	while (x) x -= x & -x, s++;
    	return s;
    }
    
    struct Q{
    	int l, r, id;
    	bool operator < (const Q &b) const {
    		if (pos[l] != pos[b.l]) return l < b.l;
    		return (pos[l] & 1) ? r < b.r : r > b.r;
    	}
    } q[N];
    
    struct E{
    	int l, r, opt, id;
    };
    
    vector<E> e[N];
    
    
    int main() {
    	scanf("%d%d%d", &n, &m, &K); t = sqrt(n);
    	for (int i = 1; i <= n; i++) scanf("%d", a + i), pos[i] = (i - 1) / t + 1;
    	for (int i = 0; i < (1 << L); i++)
    		if (get(i) == K) d[++tot] = i;
    	for (int i = 1; i <= n; i++) {
    		for (int j = 1; j <= tot; j++) cnt[a[i] ^ d[j]]++;
            f[i] = cnt[a[i + 1]];
    	}
    	for (int i = 0; i < (1 << L); i++) cnt[i] = 0;
    	for (int i = 1; i <= m; i++)
    		scanf("%d%d", &q[i].l, &q[i].r), q[i].id = i;
    	sort(q + 1, q + 1 + m);
    	int l = 1, r = 0;
    	for (int i = 1; i <= m; i++) {
    		if (l < q[i].l) e[r].push_back((E) { l, q[i].l - 1, -1, i });
    		while (l < q[i].l) res[i] += f[l - 1] + !K, l++;
    		if (l > q[i].l) e[r].push_back((E) { q[i].l, l - 1, 1, i });
    		while (l > q[i].l) res[i] -= f[l - 2] + !K, l--;
    		if (r < q[i].r) e[l - 1].push_back((E) { r + 1, q[i].r, -1, i });
    		while (r < q[i].r) res[i] += f[r++];
    		if (r > q[i].r) e[l - 1].push_back((E) { q[i].r + 1, r, 1, i });
    		while (r > q[i].r) res[i] -= f[--r];
    	}
    	for (int i = 1; i <= n; i++) {
    		for (int j = 1; j <= tot; j++) cnt[a[i] ^ d[j]]++;
    		for (int j = 0; j < e[i].size(); j++) {
    			E u = e[i][j];
    			for (int k = u.l; k <= u.r; k++)
    				res[u.id] += cnt[a[k]] * u.opt;
    		}
    	}
    	for (int i = 1; i <= m; i++) res[i] += res[i - 1], ans[q[i].id] = res[i];
    	for (int i = 1; i <= m; i++) printf("%lld
    ", ans[i]);
    	return 0;
    }
    
  • 相关阅读:
    通过SecureCRT向远程Linux主机上传和下载文件
    Oracle JDK究竟从哪个版本开始收费?
    设计模式之工厂方法(FactoryMethod)模式
    设计模式之构建者(Builder)模式
    Word文档中多个编号放同一行的方法(非技术)
    局域网内客户端无法使用机器名连接SQLServer服务器
    八、springboot 简单优雅的通过docker-compose 构建
    五、docker-compose开锋(docker 三剑客)
    四、docker 仓库(让我们的镜像有处可存)
    三、DockerFile 定制属于自己的专属镜像
  • 原文地址:https://www.cnblogs.com/dmoransky/p/13732724.html
Copyright © 2011-2022 走看看