• 莫队二次离线


    用途

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

    为了统计答案,我们需要维护额外的结构或信息,导致时间复杂度从 (O(nsqrt{n})) 变成了 (O(nksqrt{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(nsqrt{n})) 次。

    假如我们顺序扫描 ([1,1],[1,2],...,[1,n]) 这些前缀,并且维护它们的信息。于是我们就相当于(O(n)) 次修改操作和 (O(nsqrt{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=inom{14}{7} = 3432) )。

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

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

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

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

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

    结合二次离线,时间就变成了 (O(np+nsqrt{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( '
    ' );
    	return 0;
    }
    
  • 相关阅读:
    有透明通道位图的传输
    WritePrivateProfileString等读写.ini配置文件
    使用TransparentBlt函数实现绘制透明位图
    宽字符和普通字符串处理函数
    C++输出十六进制、八进制和二进制数
    判断指定窗口是否被其他窗口遮挡
    c++ 复制图片到剪切板
    用默认浏览器打开一个网址
    Bitmap:bmWidthBytes
    C/C++ BMP图像的放大缩小(双线性插值)及彩色转黑白
  • 原文地址:https://www.cnblogs.com/crashed/p/13562320.html
Copyright © 2020-2023  润新知