【luogu P6466】分散层叠算法(Fractional Cascading)(二分)(模板)

分散层叠算法(Fractional Cascading)

题目链接:luogu P6466

题目大意

给你 k 个长度为 n 的有序数组,然后要你在线查询:
给你一个数 x,求每个数组 x 的非严格后继的异或和。

思路

首先考虑一些暴力之类的?
不难想到 O ( n k log ⁡ n ) O(nk\log n) O(nklogn) 的二分暴力。

然后你考虑一个神奇的方法:
弄一个二维数组 B B B,然后第 i i i 维除了放第 i i i 个有序数组,你还可以放第 i + 1 i+1 i+1 维的数组中偶数位的部分。(这个可以归并排序实现)
这样有什么用呢?你会发现你如果求这个数组二分的答案,它原来的有序数组的答案其实可以用这个的答案在二分得到(而且其实就没有必要二分了,直接找后面第一个是原来有序数组的数,可以在归并排序的时候得到)。
然后你要求 i + 1 i+1 i+1 的数组的答案的时候,你会发现你又像上面一样搞,位置的相差竟然至多是 1 1 1

然后你考虑你 B B B 数组每一维的数量,考虑每个的贡献,当前的是 1 1 1,隔一个是 1 2 \dfrac{1}{2} 21,然后 1 4 \dfrac{1}{4} 41,所以总的来讲不到 2 2 2

然后你就可以 O ( n k + q log ⁡ n + q k ) O(nk+q\log n+qk) O(nk+qlogn+qk) 做了。


然后可以小小扩展一下,如果给定是一个图然后每个点有一个数列,度数不超过一个值 d d d,每次询问是求图上一条链的每个点的答案。
那我们这里就是隔 d d d 个选一个,然后预处理的部分就是乘上一个 log ⁡ d \log d logd

它其实有一个类似均摊的感觉,所以你可以和分块之类的结合起来,但是我就不会啦awa。
(不过你可以参考 IOI 2020 中国国家候选队论文《浅谈利用分散层叠算法对经典分块问题的优化》)

代码

#include<cstdio>
#include<algorithm>

using namespace std;

const int N = 10000 + 10;
const int K = 100 + 10;
const int Q = 500000 + 10;
struct node {
	int x, nxt1, nxt2; 
}b[K][N << 1];
int n, k, q, d, lstans, sz[K], a[K][N], fir[N << 1], x;

int main() {
	scanf("%d %d %d %d", &n, &k, &q, &d);
	for (int i = 1; i <= k; i++)
		for (int j = 1; j <= n; j++) scanf("%d", &a[i][j]);
	
	sz[k] = n; for (int i = 1; i <= n; i++) b[k][i] = (node){a[k][i], i, 0};
	for (int i = k - 1; i >= 1; i--) {
		int x = 1, y = 2, nxt1 = 0, nxt2 = 0;
		while (x <= n && y <= sz[i + 1]) {
			if (a[i][x] < b[i + 1][y].x) nxt1 = x, b[i][++sz[i]] = (node){a[i][x], nxt1, nxt2 + 1}, x++;
				else nxt2 = y, b[i][++sz[i]] = (node){b[i + 1][y].x, nxt1 + 1, nxt2}, y = y + 2;
		}
		while (x <= n) nxt1 = x, b[i][++sz[i]] = (node){a[i][x], nxt1, nxt2 + 1}, x++;
		while (y <= sz[i + 1]) nxt2 = y, b[i][++sz[i]] = (node){b[i + 1][y].x, nxt1 + 1, nxt2}, y = y + 2;
	}
	for (int i = 1; i <= sz[1]; i++) fir[i] = b[1][i].x;
	
	for (int t = 1; t <= q; t++) {
		scanf("%d", &x); x ^= lstans;
		
		int p = lower_bound(fir + 1, fir + sz[1] + 1, x) - fir;
		lstans = 0;
		for (int i = 1; i <= k; i++) {
			while (p <= sz[i] && b[i][p].x < x) p++;
			while (p >= 2 && b[i][p - 1].x >= x) p--;
			if (p <= sz[i]) {
				lstans ^= a[i][b[i][p].nxt1];
				p = b[i][p].nxt2;
			}
			else p = sz[i + 1] + 1;
		}
		
		if (t % d == 0) {
			printf("%d\n", lstans);
		}
	}
	
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值