数颜色/维护队列 [做题笔记]

此生第一道不贺题解\(AC\)的分块蓝题!!!

题目描述

墨墨@hs_mo购买了一套 \(N\) 支彩色画笔(其中有些颜色可能相同),摆成一排,你需要回答墨墨的提问。墨墨会向你发布如下指令:

  1. \(Q\ L\ R\) 代表询问你从第 \(L\) 支画笔到第 \(R\) 支画笔中共有几种不同颜色的画笔。

  2. \(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--;\)可读性\(--;)\)

最为难绷的是:

  1. 我把块长 \(len\) 开成 \(\sqrt{n}\) ,会 \(TLE\ \ 8,9,10\) 三个点;
  2. 我把块长 \(len\) 开成 \(n^{\frac{2}{3}}\) ,会 \(TLE\ \ 11,12,13\) 三个点;
  3. 于是我一怒之下怒了一下,把 \(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\) ,如果存在:

  1. \(getnum(i,j,k)=0,i \leq vx \leq j \leq tot\) ,显然此时\(i,j\)中没有 \(k\) ,则 \(spx[i][j]++\)
  2. 修改前原数为 \(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]数颜色/维护队列 (分块)的更多相关文章

  1. bzoj2120 / P1903 [国家集训队]数颜色 / 维护队列(带修改莫队)

    P1903 [国家集训队]数颜色 / 维护队列 带修改的莫队 在原有指针$(l,r)$上又添加了时间指针$t$ 贴一段dalao的解释 带修改的莫队,和原版莫队相比,多了一个时间轴 原版莫队是将区间( ...

  2. BZOJ2120&&2453 数颜色&&维护队列

    2453: 维护队列 Time Limit: 10 Sec Memory Limit: 128 MB Submit: 1442 Solved: 678 [Submit][Status][Discuss ...

  3. 题解 洛谷P1903/BZOJ2120【[国家集训队]数颜色 / 维护队列】

    对于不会树套树.主席树的本蒟蒻,还是老老实实的用莫队做吧.... 其实这题跟普通莫队差不了多远,无非就是有了一个时间,当我们按正常流程排完序后,按照基本的莫队来,做莫队时每次循环对于这一次操作,我们在 ...

  4. 洛谷 P1903 [国家集训队]数颜色 / 维护队列

    墨墨购买了一套N支彩色画笔(其中有些颜色可能相同),摆成一排,你需要回答墨墨的提问.墨墨会向你发布如下指令: 1. \(Q\) \(L\) \(R\) 代表询问你从第L支画笔到第R支画笔中共有几种不同 ...

  5. P1903 [国家集训队]数颜色 / 维护队列(带修莫队)

    题目描述: 墨墨购买了一套N支彩色画笔(其中有些颜色可能相同),摆成一排,你需要回答墨墨的提问.墨墨会向你发布如下指令: 1. Q L R代表询问你从第L支画笔到第R支画笔中共有几种不同颜色的画笔. ...

  6. P1903 [国家集训队]数颜色 / 维护队列 带修改莫队

    题目描述 墨墨购买了一套N支彩色画笔(其中有些颜色可能相同),摆成一排,你需要回答墨墨的提问.墨墨会向你发布如下指令: 1. Q L R代表询问你从第L支画笔到第R支画笔中共有几种不同颜色的画笔. 2 ...

  7. [bzoj2120][数颜色] (暴力 or 分块)

    Description 墨墨购买了一套N支彩色画笔(其中有些颜色可能相同),摆成一排,你需要回答墨墨的提问.墨墨会像你发布如下指令: 1. Q L R代表询问你从第L支画笔到第R支画笔中共有几种不同颜 ...

  8. bzoj2120: 数颜色 [莫队][分块]

    Description 墨墨购买了一套N支彩色画笔(其中有些颜色可能相同),摆成一排,你需要回答墨墨的提问.墨墨会像你发布如下指令: 1. Q L R代表询问你从第L支画笔到第R支画笔中共有几种不同颜 ...

  9. P1903 [国家集训队]数颜色 / 维护队列

    思路 带修莫队的板子 带修莫队只需要多维护一个时间的指针即可,记录一下每个询问在第几次修改之后,再回退或者前进几个修改操作 排序的时候如果a.l和b.l在一个块里,就看r,如果a.r和b.r在一个块里 ...

  10. P1903 [国家集训队]数颜色 / 维护队列 带修改的莫队

    \(\color{#0066ff}{ 题目描述 }\) 墨墨购买了一套N支彩色画笔(其中有些颜色可能相同),摆成一排,你需要回答墨墨的提问.墨墨会向你发布如下指令: 1. Q L R代表询问你从第L支 ...

随机推荐

  1. 20.4 延迟加载DLL--《Windows核心编程》

    延迟加载的 DLL 是个隐含链接的 DLL,它实际上要等到你的代码试图引用 DLL 中包含的一个符号时才进行加载. DLL延迟加载技术的原理,就是从导入表中去掉某dll这一项,等到正式调用DLL的时候 ...

  2. Git操作--Pycharm

    声明: 1)仅作为个人学习,如有冒犯,告知速删! 2)不想误导,如有错误,不吝指教! 一--在idea中配置git: 1. 更改git的安装路径:file-->settings,找到versio ...

  3. P2898 [USACO08JAN] Haybale Guessing G 题解

    题目传送门 前置知识 二分答案 | 并查集 解法 对条件的合法性判断其他题解已经讲得很明白了,这里不再赘述.这里主要讲一下用并查集实现黑白染色问题. 以下内容称被覆盖为黑色,不被覆盖为白色. 本题因为 ...

  4. NC19910 [CQOI2007]矩形RECT

    题目链接 题目 题目描述 给一个a*b矩形,由a*b个单位正方形组成.你需要沿着网格线把它分成分空的两部分,每部分所有格子连通,且至少有一个格子在原矩形的边界上."连通"是指任两个 ...

  5. Linux dmesg命令使用方法详解

    一.命令简介  dmesg(display message)命令用于显示开机信息.kernel 会将开机信息存储在 ring buffer 中.您若是开机时来不及查看信息,可利用 dmesg 来查看. ...

  6. 使用JS保存数据

    1 保存到text中 demo1.html <!DOCTYPE html> <html> <head> <meta charset="UTF-8&q ...

  7. docker 常用命令 快捷命令

    一.查询节点 docker ps -a 二.docker重启停止 systemctl restart docker systemctl stop docker docker restart * 三.一 ...

  8. win32-StretchDIBits - PrintDlg

    使用StretchDIBits将位图数据传输到printer的dc中 #include <Windows.h> #include <algorithm> int main() ...

  9. Kotlin 协程五 —— 在Android 中使用 Kotlin 协程

    目录 一.Android MVVM 结构 二.添加依赖 三.在后台线程中执行 3.1 协程解决了什么问题 3.2 保证主线程安全 3.3 withContext 的性能 四.结构化并发 4.1 追踪协 ...

  10. 【Azure 应用服务】如果发现当前使用的订阅无法在China North 3 区中创建App Service服务,如何来解决这个问题呢?

    问题描述 在创建App Service服务时,突然发现无法选择China North 3区域,如何来解决这个问题呢? 问题解答 根据Azure中服务都需要在订阅中注册的原理,因为China North ...