[bzoj2120]数颜色/维护队列 (分块)
数颜色/维护队列 [做题笔记]
此生第一道不贺题解\(AC\)的分块蓝题!!!
题目描述
墨墨@hs_mo购买了一套 \(N\) 支彩色画笔(其中有些颜色可能相同),摆成一排,你需要回答墨墨的提问。墨墨会向你发布如下指令:
\(Q\ L\ R\) 代表询问你从第 \(L\) 支画笔到第 \(R\) 支画笔中共有几种不同颜色的画笔。
\(R\ P\ C\) 把第 \(P\) 支画笔替换为颜色 \(C\)。
为了满足墨墨的要求,你知道你需要干什么了吗?
输入
6 5
1 2 3 4 5 5
Q 1 4
Q 2 6
R 1 2
Q 1 4
Q 2 6
输出
4
4
3
4
\(n,m \leq 10000\)
所有的输入数据中出现的所有整数均大于等于 \(1\) 且不超过 \(10^6\)。
一句话概括,给定序列 \(a\) ,要查询 \([l,r]\) 中共有多少个不一样的数,可以修改位置 \(x\) 的值。
做题历程 废话可忽略
学校\(OJ\)数据水,但是luogu ……
一开始想贺题解,全是带修莫队,正当我想要颓题解学莫队时,@xrlong跟我说分块可过,于是推了十几分钟,发现分块确实可做,虽然当时思路有很多纰漏,于是拍了好几\(w\)组数据,狂调一下午,终于擦着边在学校\(OJ\)过了,接着疯狂优化…… \((time--;\)可读性\(--;)\)
最为难绷的是:
- 我把块长 \(len\) 开成 \(\sqrt{n}\) ,会 \(TLE\ \ 8,9,10\) 三个点;
- 我把块长 \(len\) 开成 \(n^{\frac{2}{3}}\) ,会 \(TLE\ \ 11,12,13\) 三个点;
- 于是我一怒之下怒了一下,把 \(len\) 开成 \(\frac{\sqrt{n}+n^{\frac{2}{3}}}{2}\),也就是取个平均,然后就…… \(AC\)了。
然后就从@CuFeO4口中得知,某些可爱的出题人会卡块长,然后他把我的块长改成\(1000\),它也过了……
上述皆为废话
思路分析
下面进入正题,我们要求区间内有几种不同的数,可以类比P4168蒲公英求区间众数,我们维护两个数组:
- \(cnt[i][j]\) 表示前 \(i\) 块数 \(j\) 出现的次数 (类似于前缀和) ,于是可以 \(O(1)\) 查询两个块之间某个数出现过的次数。空间复杂度 \(O(M \times 10^6)\) ,他们说可过,但是我的码不行,于是一波去重,再用一个\(t\)数组,\(t[x]\) 表示数 \(x\) 映射的结果(这很显然是离散化)。然后空间就降到了 \(O(M \times N),M\) 是块的数量。
int t[MAXN],a[N],m;
void LSH()
{
for(int i=1;i<=n;i++) a[i]=c[i];
sort(a+1,a+n+1);
m=unique(a+1,a+n+1)-a-1;
for(int i=1;i<=m;i++)
t[a[i]]=i;
for(int i=1;i<=n;i++)
c[i]=t[c[i]];
//一般都是用lower_bound查找位置,但是1e6的数组我们开得起,干脆直接O(1),加上后面会提到的一些原因,所以我们这样进行nb的离散化
return;
}
- \(spx[i][j]\) 表示第 \(i\) 到 \(j\) 块中不同的数的数量(这个还是挺好想的)。空间 \(O(M^2)\)。
- \(vis[i]\) 记录数 \(i\) 被访问的状态,用于预处理和查询操作。空间 \(O(10^6)\)。
接下来是预处理。
- 处理\(cnt\)
for(int i=1;i<=tot;++i)
{
for(int j=L[i];j<=R[i];++j)
{
v[j]=i;//这相当于belong数组,个人马蜂
cnt[i][c[j]]++;
}
for(int j=1;j<=m;++j)
cnt[i][j]+=cnt[i-1][j];
}
- 处理\(spx\)
#define getnum(A,B,C) (cnt[B][C]-cnt[A-1][C])//块A至块B中C的数量
for(int i=1;i<=tot;++i)
{
for(int j=i;j<=tot;++j)
{
spx[i][j]=spx[i][j-1];
for(int k=L[j];k<=R[j];++k)
{
if((!getnum(i,j-1,c[k]))&&(!vis[c[k]]))//这很好理解
{
vis[c[k]]=1;
spx[i][j]++;
}
}
for(int k=L[j];k<=R[j];++k) vis[c[k]]=0;//O(len)清零
}
}
- 所以总的预处理
#define getnum(A,B,C) (cnt[B][C]-cnt[A-1][C])
int c[N],v[N],L[M],R[M],tot,len;
int cnt[M][N],spx[M][M];
void init()
{
LSH();
len=(sqrt(n)+pow(n,2.0/3))/2;//为啥是这个nb的块长,废话中已提到。。。
// len=sqrt(n);
// len=pow(n,2.0/3);
tot=(n-1)/len+1;
for(int i=1;i<=tot;++i)
{
L[i]=R[i-1]+1;
R[i]=L[i]+len-1;
}
R[tot]=n;
for(int i=1;i<=tot;++i)
{
for(int j=L[i];j<=R[i];++j)
{
v[j]=i;
cnt[i][c[j]]++;
}
for(int j=1;j<=m;++j)
cnt[i][j]+=cnt[i-1][j];
}
for(int i=1;i<=tot;++i)
{
for(int j=i;j<=tot;++j)
{
spx[i][j]=spx[i][j-1];
for(int k=L[j];k<=R[j];k++)
{
if((!getnum(i,j-1,c[k]))&&(!vis[c[k]]))
{
vis[c[k]]=1;
spx[i][j]++;
}
}
for(int k=L[j];k<=R[j];k++) vis[c[k]]=0;
}
}
}
重头戏来喽!单点修改,将位置 \(x\) 改为 \(k\)
首先肯定要将 \(k\) 离散化,但是——原序列里不一定有 \(k\) ,此乃本题坑人之处。因此我们上文的离散化方法就很香了。
if(!t[k])//k没有出现过
t[k]=++m;//k离散化为m+1
k=t[k];
下一步,修改 \(spx\) 数组,这是精髓。
如图设要修改的 \(x\) 所在块为 \(vx\) ,如果存在:
- \(getnum(i,j,k)=0,i \leq vx \leq j \leq tot\) ,显然此时\(i,j\)中没有 \(k\) ,则 \(spx[i][j]++\)
- 修改前原数为 \(cx\) ,当 \(getnum(i,j,cx)=1,i \leq vx \leq j \leq tot\) 时,显然此时 \(i,j\)中删去 \(cx\) 就没有了,则 \(spx[i][j]--\)
很好想吧?这是我的 优化.max.plus
int vx=v[x],cx=c[x];
c[x]=k;
if(!getnum(vx,vx,k))
for(int i=vx;i>=1;--i)
for(int j=vx;j<=tot;++j)
{
if(getnum(i,j,k)) break;
spx[i][j]++;
}
if(getnum(vx,vx,cx)==1)
for(int i=vx;i>=1;--i)
for(int j=vx;j<=tot;++j)
{
if(getnum(i,j,cx)!=1) break;
spx[i][j]--;
}
最后改一下 \(cnt\) 就好了
for(int i=vx;i<=tot;i++)
cnt[i][k]++,cnt[i][cx]--;
- 最终的 \(modify()\)
void modify(int x,int k)
{
if(!t[k]) t[k]=++m;
k=t[k];
if(c[x]==k) return;
int vx=v[x],cx=c[x];
c[x]=k;
if(!getnum(vx,vx,k))
{
for(int i=vx;i>=1;--i)
{
for(int j=vx;j<=tot;j++)
{
if(getnum(i,j,k)) break;
spx[i][j]++;
}
}
}
if(getnum(vx,vx,cx)==1)
{
for(int i=vx;i>=1;--i)
{
for(int j=vx;j<=tot;j++)
{
if(getnum(i,j,cx)!=1) break;
spx[i][j]--;
}
}
}
for(int i=vx;i<=tot;i++)
cnt[i][k]++,cnt[i][cx]--;
然后就是很水的 \(query()\)
int query(int l,int r)
{
int res=0;
int vl=v[l],vr=v[r];
if(vr-vl<=1){
for(int i=l;i<=r;i++)
if(!vis[c[i]])
res++,vis[c[i]]=1;
for(int i=l;i<=r;i++) vis[c[i]]=0;
return res;
}
res=spx[vl+1][vr-1];
for(int i=l;i<=R[vl];i++)
if((!getnum(vl+1,vr-1,c[i])) && (!vis[c[i]]))
res++,vis[c[i]]=1;
for(int i=r;i>=L[vr];--i)
if((!getnum(vl+1,vr-1,c[i])) && (!vis[c[i]]))
res++,vis[c[i]]=1;
for(int i=l;i<=R[vl];i++) vis[c[i]]=0;
for(int i=L[vr];i<=r;i++) vis[c[i]]=0;
return res;
}
最后吐槽一下,洛谷数据真的麻,本题轻微卡常(谷上卡常严重,对分块做法及其不友好)
\(AC \ \ code\)
具体注释见上文
#include<bits/stdc++.h>
using namespace std;
#define getnum(A,B,C) (cnt[B][C]-cnt[A-1][C])
const int MAXN=1e6+10;
#define N 200000
#define M 1100
#define read read()
#define pt puts("")
inline int read
{
int x=0,f=1;char c=getchar();
while(c<'0'||c>'9') {if(c=='-') f=-1;c=getchar();}
while(c>='0'&&c<='9') x=(x<<3)+(x<<1)+c-'0',c=getchar();
return f*x;
}
void write(int x)
{
if(x<0) putchar('-'),x=-x;
if(x>9) write(x/10);
putchar(x%10+'0');
return;
}
int n,T;
int c[N],v[N],L[M],R[M],tot,len;
int cnt[M][N],spx[M][M];
int t[MAXN],a[N],m;
bool vis[N];
void LSH()
{
for(int i=1;i<=n;i++) a[i]=c[i];
sort(a+1,a+n+1);
m=unique(a+1,a+n+1)-a-1;
for(int i=1;i<=m;i++)
t[a[i]]=i;
for(int i=1;i<=n;i++)
c[i]=t[c[i]];
return;
}
void init()
{
LSH();
len=(sqrt(n)+pow(n,2.0/3))/2;
// len=sqrt(n);
// len=pow(n,2.0/3);
tot=(n-1)/len+1;
for(int i=1;i<=tot;i++)
{
L[i]=R[i-1]+1;
R[i]=L[i]+len-1;
}
R[tot]=n;
for(int i=1;i<=tot;i++)
{
for(int j=L[i];j<=R[i];j++)
{
v[j]=i;
cnt[i][c[j]]++;
}
for(int j=1;j<=m;j++)
cnt[i][j]+=cnt[i-1][j];
}
for(int i=1;i<=tot;i++)
{
for(int j=i;j<=tot;j++)
{
spx[i][j]=spx[i][j-1];
for(int k=L[j];k<=R[j];k++)
{
if(cnt[j-1][c[k]]-cnt[i-1][c[k]]==0&&vis[c[k]]==0)
{
vis[c[k]]=1;
spx[i][j]++;
}
}
for(int k=L[j];k<=R[j];k++) vis[c[k]]=0;
}
}
}
void modify(int x,int k)
{
if(!t[k]) t[k]=++m;
k=t[k];
if(c[x]==k) return;
int vx=v[x],cx=c[x];
c[x]=k;
if(!getnum(vx,vx,k))
{
for(int i=vx;i>=1;--i)
{
for(int j=vx;j<=tot;j++)
{
if(getnum(i,j,k)) break;
spx[i][j]++;
}
}
}
if(getnum(vx,vx,cx)==1)
{
for(int i=vx;i>=1;--i)
{
for(int j=vx;j<=tot;j++)
{
if(getnum(i,j,cx)!=1) break;
spx[i][j]--;
}
}
}
for(int i=vx;i<=tot;i++)
cnt[i][k]++,cnt[i][cx]--;
}
int query(int l,int r)
{
int res=0;
int vl=v[l],vr=v[r];
if(vr-vl<=1){
for(int i=l;i<=r;i++)
if(!vis[c[i]])
res++,vis[c[i]]=1;
for(int i=l;i<=r;i++) vis[c[i]]=0;
return res;
}
res=spx[vl+1][vr-1];
for(int i=l;i<=R[vl];i++)
if((!getnum(vl+1,vr-1,c[i])) && (!vis[c[i]]))
res++,vis[c[i]]=1;
for(int i=r;i>=L[vr];--i)
if((!getnum(vl+1,vr-1,c[i])) && (!vis[c[i]]))
res++,vis[c[i]]=1;
for(int i=l;i<=R[vl];i++) vis[c[i]]=0;
for(int i=L[vr];i<=r;i++) vis[c[i]]=0;
return res;
}
signed main()
{
n=read,T=read;
for(int i=1;i<=n;i++) c[i]=read;
init();
char op;int x,y;
while(T--)
{
cin>>op;x=read,y=read;
if(op=='Q')
write(query(x,y)),pt;
else
modify(x,y);
}
return 0;
}
推荐一下呗~
[bzoj2120]数颜色/维护队列 (分块)的更多相关文章
- bzoj2120 / P1903 [国家集训队]数颜色 / 维护队列(带修改莫队)
P1903 [国家集训队]数颜色 / 维护队列 带修改的莫队 在原有指针$(l,r)$上又添加了时间指针$t$ 贴一段dalao的解释 带修改的莫队,和原版莫队相比,多了一个时间轴 原版莫队是将区间( ...
- BZOJ2120&&2453 数颜色&&维护队列
2453: 维护队列 Time Limit: 10 Sec Memory Limit: 128 MB Submit: 1442 Solved: 678 [Submit][Status][Discuss ...
- 题解 洛谷P1903/BZOJ2120【[国家集训队]数颜色 / 维护队列】
对于不会树套树.主席树的本蒟蒻,还是老老实实的用莫队做吧.... 其实这题跟普通莫队差不了多远,无非就是有了一个时间,当我们按正常流程排完序后,按照基本的莫队来,做莫队时每次循环对于这一次操作,我们在 ...
- 洛谷 P1903 [国家集训队]数颜色 / 维护队列
墨墨购买了一套N支彩色画笔(其中有些颜色可能相同),摆成一排,你需要回答墨墨的提问.墨墨会向你发布如下指令: 1. \(Q\) \(L\) \(R\) 代表询问你从第L支画笔到第R支画笔中共有几种不同 ...
- P1903 [国家集训队]数颜色 / 维护队列(带修莫队)
题目描述: 墨墨购买了一套N支彩色画笔(其中有些颜色可能相同),摆成一排,你需要回答墨墨的提问.墨墨会向你发布如下指令: 1. Q L R代表询问你从第L支画笔到第R支画笔中共有几种不同颜色的画笔. ...
- P1903 [国家集训队]数颜色 / 维护队列 带修改莫队
题目描述 墨墨购买了一套N支彩色画笔(其中有些颜色可能相同),摆成一排,你需要回答墨墨的提问.墨墨会向你发布如下指令: 1. Q L R代表询问你从第L支画笔到第R支画笔中共有几种不同颜色的画笔. 2 ...
- [bzoj2120][数颜色] (暴力 or 分块)
Description 墨墨购买了一套N支彩色画笔(其中有些颜色可能相同),摆成一排,你需要回答墨墨的提问.墨墨会像你发布如下指令: 1. Q L R代表询问你从第L支画笔到第R支画笔中共有几种不同颜 ...
- bzoj2120: 数颜色 [莫队][分块]
Description 墨墨购买了一套N支彩色画笔(其中有些颜色可能相同),摆成一排,你需要回答墨墨的提问.墨墨会像你发布如下指令: 1. Q L R代表询问你从第L支画笔到第R支画笔中共有几种不同颜 ...
- P1903 [国家集训队]数颜色 / 维护队列
思路 带修莫队的板子 带修莫队只需要多维护一个时间的指针即可,记录一下每个询问在第几次修改之后,再回退或者前进几个修改操作 排序的时候如果a.l和b.l在一个块里,就看r,如果a.r和b.r在一个块里 ...
- P1903 [国家集训队]数颜色 / 维护队列 带修改的莫队
\(\color{#0066ff}{ 题目描述 }\) 墨墨购买了一套N支彩色画笔(其中有些颜色可能相同),摆成一排,你需要回答墨墨的提问.墨墨会向你发布如下指令: 1. Q L R代表询问你从第L支 ...
随机推荐
- (C语言)每日代码||2023.12.23||for循环的循环条件部分可以是数字
#include <stdio.h> int main() { int i = 3; for (i; i; i--) { printf("%d\n", i); } // ...
- FDMemTable用法
procedure TForm1.FormCreate(Sender: TObject); Var i:integer; begin // i:=15; self.FDMemTable1.FieldD ...
- 使用7-zip进行分卷压缩和解分卷压缩(Windows和Linux)
现在一共有10个视频,一共313M,我对该文件夹进行分卷压缩,每个tar包100M,压缩过程如下: Windows环境首先选中所有的压缩包,然后在压缩包上单击鼠标右键,然后选择7-Zip,再选择提取到 ...
- C# 重绘图片.图片加字,加矩形,加圆,加线,根据XY坐标修改RGB
using System; using System.Drawing; using System.Drawing.Drawing2D; using System.IO; using System.Ne ...
- ABC 340
忘记打了,VP 了一把,前五题都是板子. F 题意:坐标系上给定一个整点 \((x,y)\),求另一个整点 \((a,b)\),满足 \((0,0),(x,y),(a,b)\) 组成的三角形面积为 \ ...
- NC20650 可爱の星空
题目链接 题目 题目描述 "当你看向她时,有细碎星辰落入你的眼睛,真好."--小可爱 在一个繁星闪烁的夜晚,卿念和清宇一起躺在郊外的草地上,仰望星空. 星语心愿,他们,想把这片星空 ...
- Linux常用的20个命令(下)
无论你是后端程序员还是前端程序员,都避免不了和Linux打交道.上篇介绍了Linux常用的20个命令其中的10个,本文继续介绍剩下的10个命令. 11.man 命令 manual的缩写,即使用手册的意 ...
- 【Android】使用Messenger实现进程间通讯
1 Messenger 简介 Messenger 类实现了 Parcelable 接口,用于进程间传输并处理消息,调用流程如下: Client 通过 bindService() 请求绑定 Serv ...
- pycharm—flask创建简单web项目
1.系统 系统 版本 OS win 10 pycharm 专业版2022.3.1 2.引入flask包 pip install flask 3.项目目录展示.代码.浏览器访问 from flask i ...
- stat模块
# stat模块定义了常数和函数,并用这些来解释os.stat().os.fstat()和os.lstat()的结果(如果这些在该平台上存在的话). stat.S_ISREG(mode) # 判断mo ...