@loj - 2461@ 「2018 集训队互测 Day 1」完美的队列
@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(n\sqrt{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(n\sqrt{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\n", 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)
@loj - 2461@ 「2018 集训队互测 Day 1」完美的队列的更多相关文章
- 【LOJ2461】「2018 集训队互测 Day 1」完美的队列(分块+双指针)
点此看题面 大致题意: 让你维护\(n\)个有限定长度的队列,每次区间往队列里加数,求每次加完后的队列里剩余元素种类数. 核心思路 这道题可以用分块+双指针去搞. 考虑求出每个操作插入的元素在队列中被 ...
- 【loj2461】【2018集训队互测Day 1】完美的队列
#2461. 「2018 集训队互测 Day 1」完美的队列 传送门: https://loj.ac/problem/2461 题解: 直接做可能一次操作加入队列同时会弹出很多数字,无法维护:一个操作 ...
- LOJ2476. 「2018 集训队互测 Day 3」蒜头的奖杯 & LOJ2565. 「SDOI2018」旧试题(莫比乌斯反演)
题目链接 LOJ2476:https://loj.ac/problem/2476 LOJ2565:https://loj.ac/problem/2565 题解 参考照搬了 wxh 的博客. 为了方便, ...
- [JZOJ6088] [BZOJ5376] [loj #2463]【2018集训队互测Day 1】完美的旅行【线性递推】【多项式】【FWT】
Description Solution 我们考虑将问题一步步拆解 第一步求出\(F_{S,i}\)表示一次旅行按位与的值为S,走了i步的方案数. 第二步答案是\(F_{S,i}\)的二维重复卷积,记 ...
- LOJ3069. 「2019 集训队互测 Day 1」整点计数(min_25筛)
题目链接 https://loj.ac/problem/3069 题解 复数真神奇. 一句话题意:令 \(f(x)\) 表示以原点 \((0, 0)\) 为圆心,半径为 \(x\) 的圆上的整点数量, ...
- 【2018集训队互测】【XSY3372】取石子
题目来源:2018集训队互测 Round17 T2 题意: 题解: 显然我是不可能想出来的……但是觉得这题题解太神了就来搬(chao)一下……Orzpyz! 显然不会无解…… 为了方便计算石子个数,在 ...
- Loj #6069. 「2017 山东一轮集训 Day4」塔
Loj #6069. 「2017 山东一轮集训 Day4」塔 题目描述 现在有一条 $ [1, l] $ 的数轴,要在上面造 $ n $ 座塔,每座塔的坐标要两两不同,且为整点. 塔有编号,且每座塔都 ...
- Loj #6073.「2017 山东一轮集训 Day5」距离
Loj #6073.「2017 山东一轮集训 Day5」距离 Description 给定一棵 \(n\) 个点的边带权的树,以及一个排列$ p\(,有\)q $个询问,给定点 \(u, v, k\) ...
- Loj 6068. 「2017 山东一轮集训 Day4」棋盘
Loj 6068. 「2017 山东一轮集训 Day4」棋盘 题目描述 给定一个 $ n \times n $ 的棋盘,棋盘上每个位置要么为空要么为障碍.定义棋盘上两个位置 $ (x, y),(u, ...
随机推荐
- 字符串常用方法(转载--https://www.cnblogs.com/ABook/p/5527341.html)
一.String类String类在java.lang包中,java使用String类创建一个字符串变量,字符串变量属于对象.java把String类声明的final类,不能有类.String类对象创建 ...
- H5C3--文本阴影text-shadow
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- logo 编程
玩了一把logo语言,好学易懂,小朋友有兴趣是个挺不错的玩意.当然也可用于一些机器人等控制 apt install ucblogo ;一个多边形 l 边长 n 边数 to sj :l :n repea ...
- ubuntu 12.4,搞定apt源
http://wiki.ubuntu.org.cn/Template:12.04source deb http://cn.archive.ubuntu.com/ubuntu/ precise main ...
- Linux预习
目录 linux系统和unix系统的简介 linux系统和unix系统的简介 unix是什么:和widows一样 特点:多用户,多任务 同一时刻,多用户同时执行多项程序,互不干扰 GNU项目 就是一个 ...
- Linux下读写UART串口的代码
Linux下读写UART串口的代码,从IBM Developer network上拿来的东西,操作比較的复杂,就直接跳过了,好在代码能用,记录一下- 两个实用的函数- //////////////// ...
- html转换成pdf
指定html转换成pdf 安装插件: npm install --save html2canvas npm install jspdf --save 引入 plugins/ htmlToPdf.js ...
- day37 05-HIbernate二级缓存:一级缓存更新同步到二级缓存及二级缓存配置文件
一级缓存的更新会自动同步到二级缓存. @SuppressWarnings("all") @Test // 将内存中的数据写到硬盘 public void demo7(){ Sess ...
- NOIP模拟 6.28
NOIP模拟赛6.28 Problem 1 高级打字机(type.cpp/c/pas) [题目描述] 早苗入手了最新的高级打字机.最新款自然有着与以往不同的功能,那就是它具备撤销功能,厉害吧. 请为这 ...
- CSS实现火焰效果
代码如下 //主要就是用css动画实现的 <!DOCTYPE html> <html lang="en"> <head> <meta ch ...