Live2D

Note/Solution -「洛谷 P6466」分散层叠算法

Description

  Link.

  给定 m 个长度为 n 的有严格升序且不包含重复元素的序列 a1,a2,,amq 个询问,每次询问给出 x,求 x 在每个序列中的非严格后继的异或和。强制在线。

  m100n104q105

Solution

  算是一种对多序列二分的优化科技/叭。

  思考两种暴力做法:

  第一种,直接在每个序列里二分求答案,则有单次 O(mlogn)

  第二种,把 m 个序列归并为一个长度为 nm 的大序列,对于其每个位置记录其在原来 m 个序列中的非严格后继。则有 O(nmlogm)O(m+log(nm)),不过空间复杂度难以接受。

  而所谓“分散层叠算法”,就是对以上两种算法的平衡——假设我们求出了序列集 b1,b2,,bm,其中 b1=a1bi (i>1) 是对 a1,a2,,ai 某种形式的“概括”,满足我们在 bi 中二分 x,能够找到 x 在实际 ai 中的后继,同时找到 xbi1 中后继的近似位置,那么就能在 O(1) 调整该位置后迭代入 bi1 的子问题啦。

  具体地,构造 bi

bi=ai,1,ai,2,,ai,n,bi1,2,bi1,4,(sorted)

即,biaibi1 的偶数位置元素构成的有序序列列。归纳可证,bi 的长度不 过 2n。同时对于 bi 中的每个元素,记录其在 ai 中的后继位置以及其在 bi1(完整的,包括奇数位置和偶数位置)中的后继位置(若不存在,设为最后一项,因为我们需要继续迭代调整)。

  预处理时间为 O(nm),结合上文查询方法,做到了 O(nm)O(logn+m)。这种 trick 常常与分块结合,可以加以扩展呢!

Code

  常数挺小的。(

/* Clearink */

#include <cstdio>
#include <algorithm>

#define rep( i, l, r ) for ( int i = l, repEnd##i = r; i <= repEnd##i; ++i )
#define per( i, r, l ) for ( int i = r, repEnd##i = l; i >= repEnd##i; --i )

inline char fgc() {
	static char buf[1 << 17], *p = buf, *q = buf;
	return p == q && ( q = buf + fread( p = buf, 1, 1 << 17, stdin ), p == q )
		? EOF : *p++;
}

inline int rint() {
	int x = 0, f = 1, s = fgc();
	for ( ; s < '0' || '9' < s; s = fgc() ) f = s == '-' ? -f : f;
	for ( ; '0' <= s && s <= '9'; s = fgc() ) x = x * 10 + ( s ^ '0' );
	return x * f;
}

template<typename Tp>
inline void wint( Tp x ) {
	if ( x < 0 ) putchar( '-' ), x = -x;
	if ( 9 < x ) wint( x / 10 );
	putchar( x % 10 ^ '0' );
}

const int MAXN = 1e4, MAXM = 100;
int n, m, q, D, a[MAXM + 5][MAXN + 5];

int len[MAXM + 5], top[MAXN * 2 + 5];
struct Atom { int val, nxa, nxb; };
Atom b[MAXM + 5][MAXN * 2 + 5];

inline void init() {
	len[m] = n;
	rep ( i, 1, n ) b[m][i] = { a[m][i], i, 0 };
	static Atom tmp[MAXN + 5];
	per ( i, m - 1, 1 ) {
		int tlen = 0;
		Atom* curb = b[i];
		for ( int j = 2; j <= len[i + 1]; j += 2 ) {
			tmp[++tlen] = { b[i + 1][j].val, 0, j };
		}
		int p = 1, q = 1;
		rep ( j, 1, n ) {
			for ( ; p <= tlen && tmp[p].val <= a[i][j]; ++p ) {
				*++curb = tmp[p], curb->nxa = j;
			}
			for ( ; q < len[i + 1] && b[i + 1][q].val <= a[i][j]; ++q );
			*++curb = { a[i][j], j, q };
		}
		for ( ; p <= tlen; *++curb = tmp[p++] );
		len[i] = curb - b[i];
		#ifdef RYBY
			printf( "b[%d]:\n", i );
			rep ( j, 1, len[i] ) {
				printf( "(%d,%d,%d) ", b[i][j].val, b[i][j].nxa, b[i][j].nxb );
			}
			putchar( '\n' );
		#endif
	}
	rep ( i, 1, len[1] ) top[i] = b[1][i].val;
}

int main() {
	n = rint(), m = rint(), q = rint(), D = rint();
	rep ( i, 1, m ) rep ( j, 1, n ) a[i][j] = rint();
	init();
	for ( int qid = 1, x, ans = 0; qid <= q; ++qid ) {
		x = rint() ^ ans, ans = 0;
		int p = std::lower_bound( top + 1, top + len[1] + 1, x ) - top;
		ans ^= a[1][b[1][p].nxa];
		#ifdef RYBY
			printf( "in %d, p=%d: %d\n", 1, p, a[1][b[1][p].nxa] );
		#endif
		rep ( i, 2, m ) {
			p = b[i - 1][p].nxb;
			for ( ; p < len[i] && b[i][p + 1].val <= x; ++p );
			for ( ; p > 1 && b[i][p - 1].val >= x; --p );
			#ifdef RYBY
				printf( "in %d, p=%d: %d\n", i, p, a[i][b[i][p].nxa] );
			#endif
			if ( a[i][b[i][p].nxa] >= x ) ans ^= a[i][b[i][p].nxa];
		}
		if ( !( qid % D ) ) wint( ans ), putchar( '\n' );
	}
	return 0;
}

posted @   Rainybunny  阅读(115)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示