• 「LOJ2461」完美的队列


    题目

    点这里看题目。

    分析

    首先,解决这个问题等价于算出每个操作在什么时候会被“完全弹出”,也就是什么时候队列中不会剩下这次操作留下来的权值了。

    对于 \(l=r\) 的操作:在进行完本次的操作之后,再向队列 \(l\) 中加入 \(a_l\) 个权值就会导致该操作的权值被弹出。

    对于 \(l<r\) 的操作:显然,我们可以看作是该操作在 \([l,r]\) 中的每个单个队列中,被弹出的时间的最大值。问题在于,我们不可能枚举每个队列来计算,怎么办呢?

    注意到,这种计算方法暗示了我们可以随意为队列分组来计算。因此,一种方法是“分块”,拆分成 \(O(\sqrt n)\) 个区间;另一种方法就是“线段树”或者“树状树组”,划分出 \(O(n)\) 个区间,并且每个操作至多覆盖 \(O(\log n)\) 个区间。

    将所有的操作区间挂到线段树结点上去,并且离线按照 DFS 顺序处理所有询问。现在所有询问都变成了全局询问,“弹出”时间与“加入”时间有了单调性。因此我们可以对于某个区间上的所有询问,按照时间做一个双指针,靠后的指针维护“什么时候会被弹出”。此时我们可以专注于修改:

    1. 完全覆盖区间的修改:我们需要知道修改的时间,因此可以用一棵线段树(或者树状数组)维护时间。

    2. 部分相交区间的修改:这一部分我们又需要考虑时间、又需要考虑位置。不过根据线段树的拆分方式,部分相交的修改的总个数是 \(O(n\log n)\),和线段树时间复杂度相同。因此,对于每个区间,暴力地存下这样的修改,然后加入到双指针流程中考虑即可。这一部分还需要另一棵线段树来维护序列。

    这样做就是正儿八经 \(O(n\log^2n)\) 的。由于线段树反复嵌套,复杂度和运行效率并没有出现直接的关联 。

    Remark.

    首先简单的观察引出的关键的思考:队列互相独立、答案可以快速合并,因此我们可以将队列分组处理。当然,从一般的数据结构的角度入手也可以得到这样的结果。

    草,为什么不想一想线段树?

    与线段树不同的地方在于,我们并没有严格遵循线段树的结构,而是将线段树看作了一种特殊的区间划分方式。这种划分方式有很好的性质:

    1. 区间个数为 \(O(n)\)

    2. 一次修改至多经过 \(O(\log n)\) 个线段树结点(粗估一下,常数大概在 \(4\) 左右)。

    3. 区间虽然会有重叠,但是依照树形结构,区间之间要么包含要么不交。

    4. 挂到线段树结点上的询问可以看作区间上的全局询问。

    现在,我们可以逐区间处理。并且,存在包含关系的区间往往会有影响,对着树形 DFS 可以帮助我们处理这种影响。

    代码

    #include <cstdio>
    #include <vector>
    
    #define rep( i, a, b ) for( int i = (a) ; i <= (b) ; i ++ )
    #define per( i, a, b ) for( int i = (a) ; i >= (b) ; i -- )
    
    const int MAXN = 1e5 + 5;
    
    template<typename _T>
    void read( _T &x ) {
    	x = 0; char s = getchar(); bool f = false;
    	while( ! ( '0' <= s && s <= '9' ) ) { f = s == '-', s = getchar(); }
    	while( '0' <= s && s <= '9' ) { x = ( x << 3 ) + ( x << 1 ) + ( s - '0' ), s = getchar(); }
    	if( f ) x = -x;
    }
    
    template<typename _T>
    void write( _T x ) {
    	if( x < 0 ) putchar( '-' ), x = -x;
    	if( 9 < x ) write( x / 10 );
    	putchar( x % 10 + '0' );
    }
    
    template<typename _T>
    inline _T Max( const _T &a, const _T &b ) {
    	return a > b ? a : b;
    }
    
    template<typename _T>
    inline _T Abs( const _T &a ) {
    	return a < 0 ? -a : a;
    }
    
    std :: vector<int> opt[MAXN];
    
    int app[MAXN], ans = 0;
    
    int qL[MAXN], qR[MAXN], qX[MAXN], ddl[MAXN];
    int A[MAXN];
    
    int N, M;
    
    namespace Time {
    	int su[MAXN];
    
    	inline void Down( int &x ) { x &= x - 1; }
    	inline void Up( int &x ) { x += x & ( - x ); }
    	inline void Update( int x, int v ) { for( ; x <= M ; Up( x ) ) su[x] += v; }
    	inline  int Query( int x ) { int ret = 0; for( ; x ; Down( x ) ) ret += su[x]; return ret; }
    	inline  int Query( const int &l, const int &r ) { return Query( r ) - Query( l - 1 ); }
    
    	inline int Search( const int &lim ) {
    		int p = 0, val = 0;
    		for( int k = 16 ; ~ k ; k -- )
    			if( p + ( 1 << k ) <= M && val + su[p | 1 << k] < lim )
    				val += su[p |= 1 << k];
    		return p + 1;
    	}
    }
    
    namespace Sequence {
    	int mx[MAXN << 2], tag[MAXN << 2];
    
    	inline void Upt( const int &x ) {
    		mx[x] = Max( mx[x << 1], mx[x << 1 | 1] );
    	}
    
    	inline void Add( const int &x, const int &delt ) {
    		mx[x] += delt, tag[x] += delt;
    	}
    
    	inline void Normalize( const int &x ) {
    		if( ! tag[x] ) return ;
    		Add( x << 1, tag[x] );
    		Add( x << 1 | 1, tag[x] );
    		tag[x] = 0;
    	}
    
    	void Build( const int &x, const int &l, const int &r ) {
    		if( l > r ) return ;
    		if( l == r ) { mx[x] = A[l]; return ; }
    		int mid = ( l + r ) >> 1;
    		Build( x << 1, l, mid );
    		Build( x << 1 | 1, mid + 1, r );
    		Upt( x );
    	}
    
    	void Modify( const int &x, const int &l, const int &r, const int &segL, const int &segR, const int &delt ) {
    		if( segL <= l && r <= segR ) { Add( x, delt ); return ; }
    		int mid = ( l + r ) >> 1; Normalize( x );
    		if( segL <= mid ) Modify( x << 1, l, mid, segL, segR, delt );
    		if( mid  < segR ) Modify( x << 1 | 1, mid + 1, r, segL, segR, delt );
    		Upt( x );
    	}
    
    	int Query( const int &x, const int &l, const int &r, const int &segL, const int &segR ) {
    		if( segL <= l && r <= segR ) return mx[x];
    		int mid = ( l + r ) >> 1; Normalize( x );
    		if( segR <= mid ) return Query( x << 1, l, mid, segL, segR );
    		if( mid  < segL ) return Query( x << 1 | 1, mid + 1, r, segL, segR );
    		return Max( Query( x << 1, l, mid, segL, segR ), Query( x << 1 | 1, mid + 1, r, segL, segR ) );
    	}
    
    	inline int Query( const int &l, const int &r ) {
    		return Query( 1, 1, N, l, r );
    	}
    
    	inline void Modify( const int &segL, const int &segR, const int &delt ) {
    		Modify( 1, 1, N, segL, segR, delt );
    	}
    }
    
    namespace GetDDL {
    	std :: vector<int> mdf[MAXN << 2];
    	bool any[MAXN << 2];
    
    	void Hang( const int &x, const int &l, const int &r, const int &segL, const int &segR, const int &qId ) {
    		if( segL <= l && r <= segR ) {
    			mdf[x].push_back( qId ), any[x] = true;
    			return ;
    		}
    		int mid = ( l + r ) >> 1; mdf[x].push_back( - qId );
    		if( segL <= mid ) Hang( x << 1, l, mid, segL, segR, qId );
    		if( mid  < segR ) Hang( x << 1 | 1, mid + 1, r, segL, segR, qId );
    	}
    	
    	void DFS( const int &u, const int &l, const int &r ) {
    		if( mdf[u].empty() ) return ;
    		int n = mdf[u].size();
    		if( any[u] ) {
    			int p = 0, q = 0;
    			for( ; p < n ; p ++ ) {
    				int tL = Abs( mdf[u][p] );
    				Sequence :: Modify( qL[tL], qR[tL], +1 );
    				if( mdf[u][p] > 0 ) {
    					bool flg = false;
    					int tR, val, pre = Time :: Query( tL - 1 );
    					while( q < p ) {
    						tR = Abs( mdf[u][q] );
    						Sequence :: Modify( qL[tR], qR[tR], -1 ), q ++;
    					}
    					while( q < n ) {
    						tR = Abs( mdf[u][q] );
    						val = Sequence :: Query( l, r ) + pre;
    						if( val <= Time :: Query( tR ) ) {
    							ddl[tL] = Max( ddl[tL], Time :: Search( val ) );
    							flg = true;
    							break;
    						}
    						Sequence :: Modify( qL[tR], qR[tR], -1 ), q ++;
    						if( Sequence :: Query( l, r )  + pre <= Time :: Query( tR ) ) {
    							ddl[tL] = Max( ddl[tL], tR ), flg = true;
    							break;
    						}
    					}
    					if( ! flg && q == n ) {
    						val = Sequence :: Query( l, r ) + pre;
    						if( val <= Time :: Query( M ) )
    							ddl[tL] = Max( ddl[tL], Time :: Search( val ) );
    						else ddl[tL] = M + 1;
    					}
    				}
    			}
    			while( q < n ) {
    				int tR = Abs( mdf[u][q] );
    				Sequence :: Modify( qL[tR], qR[tR], -1 ), q ++;
    			}
    		}
    		if( l == r ) return ;
    		int mid = ( l + r ) >> 1;
    		for( int i = 0 ; i < n ; i ++ ) if( mdf[u][i] > 0 ) 
    			Time :: Update( mdf[u][i], +1 );
    		DFS( u << 1, l, mid );
    		DFS( u << 1 | 1, mid + 1, r );
    		for( int i = 0 ; i < n ; i ++ ) if( mdf[u][i] > 0 ) 
    			Time :: Update( mdf[u][i], -1 );
    	}
    
    	void Work() {
    		Sequence :: Build( 1, 1, N );
    		DFS( 1, 1, N );
    	}
    }
    
    int main() {
    	read( N ), read( M );
    	rep( i, 1, N ) read( A[i] );
    	rep( i, 1, M ) {
    		read( qL[i] ), read( qR[i] ), read( qX[i] );
    		GetDDL :: Hang( 1, 1, N, qL[i], qR[i], i );
    	}
    	GetDDL :: Work();
    	rep( i, 1, M ) {
    		opt[ddl[i]].push_back( qX[i] );
    		int n = opt[i].size();
    		rep( j, 0, n - 1 )
    			ans -= ! ( -- app[opt[i][j]] );
    		ans += ! ( app[qX[i]] ++ );
    		write( ans ), putchar( '\n' );
    	}
    	return 0;
    }
    
  • 相关阅读:
    总结:工作 + 学习 (2019年)
    JVM 理解性学习(一)
    渗透神器cobalt strike在数字杀软环境下的使用
    普通路由器刷开源固件DD-WRT的简单过程
    云烟渗透题总结
    对thinkphp5.0框架的实例学习
    内网渗透 关于GPO
    简单尝试利用维控LeviStudioU的一栈缓冲区溢出漏洞
    试写foxit reader的ConvertToPDF功能的wrapper
    第05章下 加载内核
  • 原文地址:https://www.cnblogs.com/crashed/p/16472639.html
Copyright © 2020-2023  润新知