码迷,mamicode.com
首页 > 其他好文 > 详细

莫队二次离线

时间:2020-08-31 13:30:20      阅读:59      评论:0      收藏:0      [点我收藏+]

标签:扫描   line   暴力   位置   假设   lin   count   ++   查询   

用途

在写序列莫队的时候,有时候我们会遇到这类问题:

为了统计答案,我们需要维护额外的结构或信息,导致时间复杂度从 \(O(n\sqrt{n})\) 变成了 \(O(nk\sqrt{n})\)

(这里我们假设序列长度 \(n\)\(m\) 同阶,否则需要重新考虑块的大小)

如果这里的信息具有可差分性,我们就可以尝试使用莫队二次离线解决这个问题。

做法

考虑我们现在已经统计的区间是 \([L,R]\) 。每次我们可以将一个新的位置 \(x\) 加入到 \([L,R]\) 里面来。

\(f([L,R],x)\) 为区间 \([L,R]\) 加入了 \(x\) 的新增贡献。由于我们知道贡献可差分,于是就有:

\[f([L,R],x)=f([1,R],x)-f([1,L],x) \]

平时我们是直接在线解决这个贡献;现在我们考虑离线。

然后你发现:

  1. 我们有 \(n\) 个前缀,因此 \(f\) 的第一个参数只有 \(n\) 种。
  2. 我们会移动 \(O(n\sqrt{n})\) 次。

假如我们顺序扫描 \([1,1],[1,2],...,[1,n]\) 这些前缀,并且维护它们的信息。于是我们就相当于\(O(n)\) 次修改操作和 \(O(n\sqrt{n})\) 次查询

注意到修改和查询很不平均,因此,如果我们适当地放大修改的时间,而缩小查询的时间,我们就可以减小时间复杂度!

另附一些优化:

  1. 莫队通常是修改 \(L-1\) 或者修改 \(R+1\) 。以修改 \(R+1\) 为例。此时 \(f([1,R],R+1)\) 只需要用一维状态描述,可以直接预处理
  2. 莫队移动的时候,区间的边界移动了一段连续的区间。比如 \([L,R]\) 变成 \([L,R+k]\) ,那么 \((R,R+k]\) 这些东西都会被加入到区间中。于是我们可以用 新增区间的范围 + \(f\) 第一维 来描述这样的查询。可以发现,这样做的话空间就优化到了 \(O(n)\)

例题

第十四次分块(前体)

先思考暴力莫队该怎么做。

首先发现,当 \(k\) 确定的时候,可用的数其实不多(存在上界 \(P=\binom{14}{7} = 3432\) )。

因此我们可以先把这些符合要求的数暴力找出来。

然后,由于异或满足:\(a\oplus b=c\Leftrightarrow a\oplus c=b\) ,因此我们可以用桶存下来每个数的出现次数。对于 \(x\) 查询的时候,我们就枚举合法异或值,并倒推出对应的数是哪一个。

此时的时间是 \(O(Pn\sqrt{n})\) 。同时我们的维护的信息是 \(O(1)\) 修改、 \(O(P)\) 查询。

现在我们利用二次离线的技巧。此时我们需要降低查询时间。

显然我们可以直接倒过来做:我们加入 \(x\) 的时候,枚举合法异或值 \(k\) ;这就说明 \(x\oplus k\) 又多了一个可配对的数,因此我们要增加 \(x\oplus k\) 的答案。查询直接 \(O(1)\) 查。此时我们就做到了 \(O(P)\) 修改, \(O(1)\) 查询。

结合二次离线,时间就变成了 \(O(np+n\sqrt{n})\)

代码:

#include <cmath>
#include <cstdio>
#include <algorithm>

typedef long long LL;

const int MAXN = 1e5 + 5, MAXS = 4e5 + 5, MAXK = 17005;

template<typename _T>
void read( _T &x )
{
	x = 0;char s = getchar();int f = 1;
	while( s > ‘9‘ || s < ‘0‘ ){if( s == ‘-‘ ) f = -1; s = getchar();}
	while( s >= ‘0‘ && s <= ‘9‘ ){x = ( x << 3 ) + ( x << 1 ) + ( s - ‘0‘ ), s = getchar();}
	x *= f;
}

template<typename _T>
void write( _T x )
{
	if( x < 0 ){ putchar( ‘-‘ ); x = ( ~ x ) + 1; }
	if( 9 < x ){ write( x / 10 ); }
	putchar( x % 10 + ‘0‘ );
}

int T;

struct Query
{
	LL ans;
	int l, r, blk, id;
	
	Query() { ans = l = r = blk = id = 0; }
	Query( int L, int R, int ID ) { ans = 0, l = L, r = R, blk = l / T, id = ID; }
	bool operator < ( const Query &b ) const { return blk == b.blk ? ( blk & 1 ? r < b.r : r > b.r ) : l < b.l; }
}Q[MAXN];

struct PSQuery
{
	int l, r, f, id;
	PSQuery() { l = r = 0, f = 1; }
	PSQuery( int L, int R, int F, int ID ) { l = L, r = R, f = F, id = ID; }
}PSQ[MAXS];

int head[MAXN], nxt[MAXS];
int cnt;

int num[MAXN], tot;

LL pre[MAXN];
LL ans[MAXN];
int buc[MAXK];
int a[MAXN];
int N, M, K;

int Ask( const int x ) { return buc[x]; }
void Clr() { for( int i = 0 ; i < 16384 ; i ++ ) buc[i] = 0; }
void Insert( const int x ) { for( int i = 1 ; i <= tot ; i ++ ) buc[num[i] ^ x] ++; }

void Add( const int p, const int left, const int rig, const int f, const int id )
{
	PSQ[++ cnt] = PSQuery( left, rig, f, id );
	nxt[cnt] = head[p], head[p] = cnt;
}

int main()
{
	read( N ), read( M ), read( K ), T = sqrt( N );
	for( int i = 1 ; i <= N ; i ++ ) read( a[i] );
	for( int i = 1, l, r ; i <= M ; i ++ ) read( l ), read( r ), Q[i] = Query( l, r, i );
	std :: sort( Q + 1, Q + 1 + M );
	
	for( int i = 0 ; i < 16384 ; i ++ )
		if( __builtin_popcount( i ) == K )
			num[++ tot] = i;
			
	for( int i = 1 ; i <= N ; i ++ ) pre[i] = Ask( a[i] ) + pre[i - 1], Insert( a[i] );
	
	int L = 1, R = 0, qL, qR;
	for( int i = 1 ; i <= M ; i ++ )
	{
		qL = Q[i].l, qR = Q[i].r;
		if( L < qL ) Add( R, L, qL - 1, -1, i ), Q[i].ans += pre[qL - 1] - pre[L - 1], L = qL;
		if( qL < L ) Add( R, qL, L - 1, +1, i ), Q[i].ans -= pre[L - 1] - pre[qL - 1], L = qL;
		if( R < qR ) Add( L - 1, R + 1, qR, -1, i ), Q[i].ans += pre[qR] - pre[R], R = qR;
		if( qR < R ) Add( L - 1, qR + 1, R, +1, i ), Q[i].ans -= pre[R] - pre[qR], R = qR;
	}
	 
	Clr();
	for( int i = 1 ; i <= N ; i ++ )
	{
		Insert( a[i] );
		for( int j = head[i] ; j ; j = nxt[j] )
			for( int k = PSQ[j].l, val ; k <= PSQ[j].r ; k ++ )
			{
				val = Ask( a[k] );
				if( k <= i && K == 0 ) val --;
				Q[PSQ[j].id].ans += PSQ[j].f * val;
			}
	}
	
	for( int i = 1 ; i <= M ; i ++ ) ans[Q[i].id] = ( Q[i].ans += Q[i - 1].ans );
	for( int i = 1 ; i <= M ; i ++ ) write( ans[i] ), putchar( ‘\n‘ );
	return 0;
}

莫队二次离线

标签:扫描   line   暴力   位置   假设   lin   count   ++   查询   

原文地址:https://www.cnblogs.com/crashed/p/13562320.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!