P1972 [SDOI2009] HH的项链 题解

前言

参考:https://www.luogu.com.cn/article/4vp3j5v4
这是一篇分块题解,大概的思路洛谷的题解里的巨佬讲的很清楚(我的思路其实和大佬的差不多),可以去看看qaq。所以本题解主要是为了加深博主理解
因为苯人为了卡常魔改代码,所以在本题解的中间具体步骤展示部分仅展示魔改前的代码(也就是被卡常但是好看一点的代码),魔改后的代码(100pts)在文章最末展示。


题意

题面传送门

有一个长度为 n 数列,第 i 个数的颜色为 ai ,有 m 次询问,询问区间 [l,r] 有多少种不同的颜色。


前置芝士

  1. 分块
  2. 卡常(拨珠这种代码十分丑陋的蒟蒻被疯狂卡常了)

思路

看到这个题是区间查询的那一刻博主就手痒想写分块。虽然博主菜的要死每次写分块都要调好久。

  • 关于整块
    1.预处理出每一块的颜色种类数量,记为 fi,i ,其中 i 代表第 i 块,则 fi,i 表示第 i 块的颜色种数(这一步主要是为第2步服务)
    2.预处理出第 i 块到第 j 块的颜色种类数量,记为 fi,j
    3.在讯问时,整块部分直接加上预处理好的数就可以了
  • 关于散块:
    直接暴力搞,就这个暴力爽

具体实现

1.输入时:记录每个数左边和右边最接近同颜色点的编号,可以用一个 ne(new)数组表示某个颜色最新的点的编号来辅助记录。

代码
for(int i=1;i<=n;i++){
	a[i]=read();
	le[i]=last[a[i]];
	last[a[i]]=i;
	ri[i]=n+1;
	ri[le[i]]=i;//block为块长,即sqrt(n)
	bel[i]=(i-1)/block+1;//bel数组表示属于哪个块
}

2.预处理:预处理的作用就是可以减少询问时的时间复杂度。具体操作如下:

  • 计算 fi,i 的方式:枚举 1n 的块,当前为第 i 块,第 j 个数,用一个 vis 数组记录第 j 个数的颜色有没有在块 i 中出现过,如果没有,则 fi,i 加一。
代码
for(int i=1;i<=bel[n];i++){
	vis.reset();
	for(int j=(i-1)*block+1;j<=min(n,i*block);j++){
		f[i][i]+=(!vis[a[j]]);
		vis[a[j]]=1;
	}
}
  • 计算 fi,j 的方式:枚举块 ijfi,jfi,j1 加上在块 j 中出现了,但是在 ij 中没有出现的颜色总数
代码
for(int i=1;i<=bel[n];i++){//bel[i]代表点i在哪个块
	for(int j=i+1;j<=bel[n];j++){
		f[i][j]=f[i][j-1];
		for(int k=(j-1)*block+1;k<=min(j*block,n);k++){
			if(bel[le[k]]<i){//block代表块长,即sqrt(n)
				f[i][j]++;
			}
		}
	}
}

3.询问:

  • l,r 的差小于等于1时特判,并暴力计算个数。
  • 其他的时候,整块的值为预处理中的 f 数组,散块暴力枚举(具体看代码注释)
代码
int query(int l,int r){
	int ret=0;
	if(bel[r]-bel[l]<=1){
		vis.reset();
		for(register int i=l;i<=r;i++){
			ret+=(!vis[a[i]]);
			vis[a[i]]=1;
		}
		return ret;
	}
	if(bel[r]-bel[l]>=2){
		ret+=f[bel[l]+1][bel[r]-1];
	}
	for(int i=l;i<=min(r,block*bel[l]);i++){
		if(bel[ri[i]]>=bel[r]){//中间的整块没有出现过这个颜色
			ret++;
		}
	}
	for(int i=(bel[r]-1)*block+1;i<=r;i++){
		if(le[i]<l){//中间的整块和左边的散块都没有出现过这个颜色
			ret++;
		}
	}
	return ret;
}

tips:

  1. 分块真的好难调啊啊啊,一定要注意边界啊啊
  2. 注意第 i 个点的颜色和第 i 个点的编号的区别
  3. 要注意的地方都是大部分分块题要注意的地方,如果实在找不出来问题重构也很好(我重构了114514次/fad

卡常

  1. vis数组使用bitset
  2. 快读快写,我还用的是getchar_unlocked()(类目)(快写是复制的模板(汗流浃背
  3. 手写maxmin(我是从网上随便搜的一个模板(大雾
  4. unsigned int和register int
  5. ++i屁用没有
  6. 换行用puts("")
  7. i=1改为i(1)

被魔改得面目全非的代码

#include<bits/stdc++.h>
using namespace std;
int n,m;
const int MAXN=1e6+5;
const int MAXM=1e3+5;
unsigned int a[MAXN],le[MAXN],ri[MAXN],last[MAXN],bel[MAXN];
bitset<MAXN>vis;
unsigned int block;
unsigned int f[MAXM][MAXM];
inline int max(int x,int y){
	return (x>y)?x:y;
}
inline int min(int x,int y){
	return (x<y)?x:y;
}
int query(int l,int r){
	int ret=0;
	if(bel[r]-bel[l]<=1){
		vis.reset();
		for(register int i(l);i<=r;i++){
			ret+=(!vis[a[i]]);
			vis[a[i]]=1;
		}
		return ret;
	}
	if(bel[r]-bel[l]>=2){
		ret+=f[bel[l]+1][bel[r]-1];
	}
	for(register int i(l);i<=min(r,block*bel[l]);i++){
		ret+=(bel[ri[i]]>=bel[r]);
	}
	if(bel[l]!=bel[r]){
		for(register int i((bel[r]-1)*block+1);i<=r;i++){
			ret+=(le[i]<l);
		}
	}
	return ret;
}
inline int read(){
	int x=0,f=1;
	char ch=getchar_unlocked();
	while(ch<'0' || ch>'9'){
		if(ch=='-'){
			f=-1;
		}
		ch=getchar_unlocked();
	}
	while(ch<='9' && ch>='0'){
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar_unlocked();
	}
	return x*f;
}
void print(int x)
{
    if(!x) return ;
    if(x<0) putchar('-'),x = -x;
    print(x/10);
    putchar(x%10+'0');
}
int main(){
	n=read();
	block=sqrt(n);
	for(register int i(1);i<=n;i++){
		a[i]=read();
		le[i]=last[a[i]];
		last[a[i]]=i;
		ri[i]=n+1;
		ri[le[i]]=i;
		bel[i]=(i-1)/block+1;
	}
	bel[n+1]=bel[n]+1;
	for(register int i(1);i<=bel[n];i++){
		vis.reset();
		for(register int j((i-1)*block+1);j<=min(n,i*block);j++){
			f[i][i]+=(!vis[a[j]]);
			vis[a[j]]=1;
		}
	}
	for(register int i(1);i<=bel[n];i++){
		for(register int j(i+1);j<=bel[n];j++){
			f[i][j]=f[i][j-1];
			for(register int k((j-1)*block+1);k<=min(j*block,n);k++){
				f[i][j]+=(bel[le[k]]<i);
			}
		}
	}
	m=read();
	for(register int i(1);i<=m;i++){
		int a,b;
		a=read(),b=read();
		print(query(a,b));
		puts("");
	}
}

结语

通过记录
本来这个题是在树状数组的题单里看到的,但是一眼就想写分块,于是就写了
呃呃呃呃分块真的好难调,调了一个晚上半个上午,我太菜了。
真的写不动题了(躺)。每天水水题解一天就过去了。

求壶关qwq,有问题的话欢迎指出
洛谷账号

作者:Le-Louvre

出处:https://www.cnblogs.com/Le-Louvre/p/18372204

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

posted @   卢浮宫  阅读(67)  评论(2编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
more_horiz
keyboard_arrow_up dark_mode palette
选择主题
点击右上角即可分享
微信分享提示