• @loj



    @description@

    小 D 有 n 个 std::queue,他把它们编号为 1 到 n。

    小 D 对每个队列有不同的喜爱程度,如果有他不怎么喜欢的队列占用了太大的内存,小 D 就会不开心。

    具体地说,如果第 i 个队列的 size() 大于 ai,小 D 就会对这个队列一直执行 pop() 直到其 size() 小等于 ai。

    现在这些队列都是空的,小 D 觉得太单调了,于是他决定做一些操作。

    每次操作都可以用 l r x 来描述,表示对编号在 [l,r] 内的所有队列执行 push(x) 操作。当然,每次操作结束后,小 D 都会用之前提到的方法来避免这些队列占用太大的内存。

    小 D 的队列很神奇,所以他能用 O(1) 的时间执行每次操作。

    相信大家的队列都能做到,于是小 D 把这道题出给大家送分。

    为了证明你确实执行了这些操作,你需要在每次操作后输出目前还在队列内的权值种数。

    输入格式
    第一行两个正整数 n,m,分别表示队列个数和操作个数。

    第二行 n 个正整数,第 i 个表示 ai。

    接下来 m 行,每行三个正整数 l r x,其中第 i 行表示第 i 次操作。

    输出格式
    输出共 m 行,每行一个非负整数,表示第 i 次操作结束后所有队列中的权值种数。

    样例
    样例输入
    3 3
    1 2 3
    1 2 1
    2 3 2
    1 3 3
    样例输出
    1
    2
    2

    样例解释
    第一次操作后,队列变成 {1}{1}{},还在队列内的权值有 1,共 1 种。

    第二次操作后,队列变成 {1}{1,2}{2},还在队列内的权值 1,2,共 2 种。

    第三次操作后,队列变成 {3}{2,3}{2,3},还在队列内的权值有 2,3,共 2 种。

    数据范围与提示
    对于所有数据,n,m,ai,x<=10^5,l<=r。

    @solution@

    @part - 0@

    先讲一些非正解的思路。
    (如果是想直接知道正解的可以直接跳到 part - 1)

    当 ai = 1 时,就相当于区间涂色,统计总颜色种类
    注意到每次涂色,最多会把一个连续颜色段分割成两个。也就是连续颜色段的个数每次最多增加 1。
    因此我们用平衡树维护一下这些颜色段即可,每次将消失的颜色段暴力删除,将颜色段暴力裂开

    当 ai ≠ 1 时,可以维护 max{ai} 棵平衡树,每次从上一棵平衡树中取出操作区间(用 splay 或非旋 treap 即可),并把这个区间覆盖到本层的这棵平衡树上。
    如果到达了这个区间内所有队列的最小容量(即 min{a[l...r]}),就暴力弹,分成两个区间继续操作。

    可以发现这个思路到这里就停了,因为这个思路始终与 max{ai} 相关。

    遇事不决先离线。我们考虑离线是否能简化问题。
    采用线段树分治类似的套路:我们尝试求出每次操作能够影响到的时间段(注意不是每种颜色的时间)。
    然后发现如果我们求出了这个值,剩下的只需要按时间从前往后扫一遍即可。

    求每次操作能够影响的时间段,其实就是求这个操作的颜色全部弹出的最早时间
    这个时间是具有单调性的:一旦在时刻 t 所有颜色均被弹出,则 t 秒之后依然全部颜色均被弹出。
    可以二分。对于操作 i,二分最早时间 t。

    至于怎么判断合法。我们维护一个线段树,每个位置的值维护为 “队列容量 - 队列的元素总数”。
    然后我们把 [i, t] 这些操作全部塞进线段树里面,查询 i 对应的区间 [li, ri] 的最大值是否为负。
    注意到如果知道 [l, r] 的线段树,我们可以 O(log) 求出 [l-1, r] 与 [l, r+1] 的线段树(类似于莫队的性质)。

    但是单纯的二分是过不了。虽然可以 分块 + 整体二分 乱搞,不过最好的方法是类似于大步小步的方法:
    我们取 (b_i = i*sqrt{n}),则一共会有 (sqrt{n}) 个 b。然后我们对于每一个 i,求出 ([i, b_{1...sqrt{n}}]) 的线段树。
    然后对于每个 i,可以先判定 i 对应的最早时间 t 在哪两个 b 之间,然后在这两个 b 之间暴力枚举。
    这个算法的复杂度为 (O(nsqrt{n}log n))

    @part - 1@

    在看完以上那些乱搞后,会觉得以上那些做法看起来非常正确。
    所以极有可能就不小心跑偏了。。。

    一样地,我们采用离线
    采用线段树分治类似的套路:我们尝试求出每次操作能够影响到的时间段(注意不是每种颜色的时间)。
    然后发现如果我们求出了这个值,剩下的只需要按时间从前往后扫一遍即可。
    求每次操作能够影响的时间段,其实就是求这个操作的颜色全部弹出的最早时间
    (对我就是直接复制 part - 0 中的话)

    注意到有一个性质:假如两个操作 i, j 的操作区间相同,且 i 在 j 之前,则 i 永远比 j 先消失。
    即:操作区间相同时,删除顺序具有单调性

    为了充分利用单调性,我们对序列分块,将一个操作区间拆成若干整块操作与最多 2 个散块操作。
    然后考虑每一个块,怎么处理涉及到这个块的整块操作与散块操作的最早删除时间。

    先考虑整块操作的删除时间。我们维护每个队列的 “队列容量 - 队列的元素总数”,再维护这个块内该值的最大值 smx。
    用个 two-points。当 smx < 0 时,左端点对应元素全部被弹出,记录此时右端点为左端点的最早时间,然后将左端点右移;否则,将右端点右移。
    至于右移的影响,整块直接改 smx,散块暴力重算(因为散块总个数为 (O(n)))。

    然后考虑散块。我们去找散块中的每一个元素算删除时间(一样地,因为散块总个数为 (O(n)))。
    两个散块操作之间的整块操作是没有区别的。于是我们处理出相邻两个散块操作之间有多少个整块操作,然后一样地一遍 two-points 搞定(这里的 two-points 只遍历散块操作)。

    这样时间复杂度为 (O(nsqrt{n})),非常优秀。

    @accepted code@

    #include<cstdio>
    #include<vector>
    #include<algorithm>
    using namespace std;
    const int MAXN = 100000;
    const int BLOCK = 320;
    int le[MAXN + 5], ri[MAXN + 5], id[MAXN + 5], bcnt;
    void init(int n) {
    	for(int i=1;i<=n;i++) {
    		if( (i-1) % BLOCK == 0 )
    			le[++bcnt] = i;
    		ri[bcnt] = i, id[i] = bcnt;
    	}
    }
    int tg[MAXN + 5], nxt[MAXN + 5];
    int arr[MAXN + 5], type[MAXN + 5], cnt;
    int l[MAXN + 5], r[MAXN + 5], x[MAXN + 5];
    int a[MAXN + 5], ed[MAXN + 5], n, m;
    vector<int>v1[MAXN + 5], v2[MAXN + 5];
    int siz[MAXN + 5];
    void solve() {
    	for(int i=1;i<=m;i++)
    		v1[i].push_back(x[i]), v2[ed[i]].push_back(x[i]);
    	int ans = 0;
    	for(int i=1;i<=m;i++) {
    		for(int j=0;j<v1[i].size();j++) {
    			if( siz[v1[i][j]] == 0 ) ans++;
    			siz[v1[i][j]]++;
    		}
    		for(int j=0;j<v2[i].size();j++) {
    			siz[v2[i][j]]--;
    			if( siz[v2[i][j]] == 0 ) ans--;
    		}
    		printf("%d
    ", ans);
    	}
    }
    int main() {
    	scanf("%d%d", &n, &m), init(n);
    	for(int i=1;i<=n;i++)
    		scanf("%d", &a[i]);
    	for(int i=1;i<=m;i++)
    		scanf("%d%d%d", &l[i], &r[i], &x[i]);
    	for(int i=1;i<=bcnt;i++) {
    		cnt = 0;
    		for(int j=1;j<=m;j++)
    			if( l[j] > ri[i] || r[j] < le[i] )
    				continue;
    			else if( l[j] <= le[i] && ri[i] <= r[j] )
    				cnt++, arr[cnt] = j, type[cnt] = 0;
    			else
    				cnt++, arr[cnt] = j, type[cnt] = 1;
    		int stg = 0, smx = 0, right = 0;
    		for(int j=le[i];j<=ri[i];j++)
    			tg[j] = a[j], smx = max(smx, tg[j]);
    		arr[cnt + 1] = m + 1;
    		for(int j=1;j<=cnt;j++) {
    			while( right <= cnt && smx - stg >= 0 ) {
    				right++;
    				if( right > cnt ) break;
    				if( type[right] == 0 )
    					stg++;
    				else {
    					smx = 0;
    					for(int k=le[i];k<=ri[i];k++) {
    						if( l[arr[right]] <= k && k <= r[arr[right]] )
    							tg[k]--;
    						smx = max(smx, tg[k]);
    					}
    				}
    			}
    			if( type[j] == 0 ) {
    				ed[arr[j]] = max(ed[arr[j]], arr[right]);
    				stg--;
    			}
    			else {
    				smx = 0;
    				for(int k=le[i];k<=ri[i];k++) {
    					if( l[arr[j]] <= k && k <= r[arr[j]] )
    						tg[k]++;
    					smx = max(smx, tg[k]);
    				}
    			}
    		}
    		int tmp = cnt + 1;
    		for(int j=cnt;j>=1;j--)
    			if( type[j] == 1 )
    				nxt[j] = tmp, tmp = j;
    		for(int j=le[i];j<=ri[i];j++) {
    			int right = 0, tot = 0;
    			for(int k=tmp;k<=cnt;k=nxt[k]) {
    				if( right < k ) {
    					right = k;
    					if( l[arr[k]] <= j && j <= r[arr[k]] )
    						tot++;
    				}
    				while( right <= cnt && tot + nxt[right] - right - 1 <= a[j] ) {
    					tot += nxt[right] - right - 1;
    					right = nxt[right];
    					if( right > cnt ) break;
    					if( l[arr[right]] <= j && j <= r[arr[right]] )
    						tot++;
    				}
    				if( l[arr[k]] <= j && j <= r[arr[k]] ) {
    					if( right > cnt ) ed[arr[k]] = max(ed[arr[k]], arr[right]);
    					else ed[arr[k]] = max(ed[arr[k]], arr[right + a[j] - tot + 1]);
    				}
    				if( l[arr[k]] <= j && j <= r[arr[k]] )
    					tot--;
    				if( right > k )
    					tot -= nxt[k] - k - 1;
    				else tot = 0;
    			}
    		}
    	}
    	solve();
    }
    

    @details@

    把 max 打成 min,然后 WA 到怀疑人生。。。
    (质疑自己的脑袋是否完整.jpg)

  • 相关阅读:
    CentOS6.3搭建Nginx代理访问MongoDB GridFS图片资源
    PHP判断变量是否存在及函数isset() 、empty()与is_null的区别
    【摘】请问make -j8 和make -j4 是什么意思?什么作用?
    关于数字、数据处理的几个PHP函数汇总
    Windows下Nginx的启动、停止等基本命令
    Git 简明教程
    PHP函数preg_replace() 正则替换所有符合条件的字符串
    如何挂载阿里云Linux服务器的“数据盘”(新购买)
    ThinkPHP模板中JS等带花括号处会被解析错误的解决办法
    移动端与PHP服务端接口通信流程设计(增强版)
  • 原文地址:https://www.cnblogs.com/Tiw-Air-OAO/p/11630457.html
Copyright © 2020-2023  润新知