首页 > 其他分享 >P1972 [SDOI2009] HH的项链 题解

P1972 [SDOI2009] HH的项链 题解

时间:2024-08-21 17:37:06浏览次数:15  
标签:bel 分块 int 题解 代码 register MAXN SDOI2009 P1972

前言

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


题意

题面传送门

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


前置芝士

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


思路

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

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

具体实现

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

代码
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;//block为块长,即sqrt(n)
	bel[i]=(i-1)/block+1;//bel数组表示属于哪个块
}

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

  • 计算\(f_{i,i}\)的方式:枚举\(1\thicksim\sqrt{n}\)的块,当前为第\(i\)块,第\(j\)个数,用一个\(vis\)数组记录第\(j\)个数的颜色有没有在块\(i\)中出现过,如果没有,则\(f_{i,i}\)加一。
代码
for(register 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;
	}
}
  • 计算\(f_{i,j}\)的方式:枚举块\(i\)和\(j\),\(f_{i,j}\)为\(f_{i,j-1}\)加上在块\(j\)中出现了,但是在\(i\thicksim j\)中没有出现的颜色总数
代码
for(register 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(register int i=l;i<=min(r,block*bel[l]);i++){
		if(bel[ri[i]]>=bel[r]){//中间的整块没有出现过这个颜色
			ret++;
		}
	}
	for(register 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.手写\(max\)和\(min\)
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;
		}
	}
//	cout<<ri[3]<<endl;
	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,有问题的话欢迎指出
洛谷账号

标签:bel,分块,int,题解,代码,register,MAXN,SDOI2009,P1972
From: https://www.cnblogs.com/Le-Louvre/p/18372204

相关文章

  • 操作系统基础之磁盘及软考高级试题解析
    概述基本概念磁盘有正反两个盘面,每个盘面有多个同心圆,每个同心圆是一个磁道,每个同心圆又被划分为多个扇区,数据就被存在扇区中。磁头首先寻找到对应磁道,然后等到磁盘进行周期旋转到指定的扇区,才能读取到对应的数据。存取时间=寻道时间+等待时间盘面号(磁头号):0~M-1;由于一......
  • 信号量、PV操作及软考高级试题解析
    信号量在并发系统中,信号量是用于控制公共资源访问权限的变量。信号量用于解决临界区问题,使得多任务环境下,进程能同步运行。此概念是由荷兰计算机科学家Dijkstra在1962年左右提出的。信号量仅仅跟踪还剩多少资源可用,不会跟踪哪些资源是可用的。信号量机制,处理进程同步和互斥的问......
  • 题解:Codeforces Round 967 (Div. 2) B [思维/构造]
    B.GeneratePermutationtimelimitpertest:1.5secondsmemorylimitpertest:256megabytesinput:standardinputoutput:standardoutputThereisanintegersequence\(a\)oflength\(n\),whereeachelementisinitially\(-1\).Misukihastwoty......
  • [ABC133D] Rain Flows into Dams 题解
    思路其实就是一道数学题。设每座山的水量为$ans_i$,大坝的水量为$w_i$,则根据题意可以得到以下方程:$$\begin{cases}w_i=\frac{ans_i+ans_{i+1}}{2}&i<n\w_i=\frac{ans_i+ans_1}{2}&i=n\end{cases}$$所以只要求出任意一个$ans$就可以求出剩余的$ans$,这里我选择求$ans_1$的......
  • CF1264D1 题解
    blog。写一个题解区没有的蠢做法,不依赖dp但是很难转到HardVersion(对于已经确定的序列,深度有单调性。就是如果答案为\(x\),那么肯定能选出深度为\(1\simx\)的子序列。记\(\operatorname{check}_s(x)\)表示check序列\(s\)能否选出深度为\(x\)的子序列。考虑如何c......
  • 题解:AT_abc140_e [ABC140E] Second Sum
    思路:双向链表+组合数学(不过你要用单调栈也没人拦着你)我们现在先抛开题面,先换个思路。我们现在求:这个数能做多少个区间的次大值。我们现在设\(l1,l2,r1,r2\)分别为左边第一个比这个数大的id,第二个比这个数大的id,右边第一个比这个数大的id,第二个比这个数大的id。竟然是......
  • 题解:Codeforces Round 967 (Div. 2) [暴力/贪心]
    A.MakeAllEqualtimelimitpertest:1secondmemorylimitpertest:256megabytesinput:standardinputoutput:standardoutputYouaregivenacyclicarray\(a_1,a_2,\ldots,a_n\).Youcanperformthefollowingoperationon\(a\)atmost\(n-......
  • 题解 |栈| #中缀表达式求值!!!!#
    描述请写一个整数计算器,支持加减乘三种运算和括号。数据范围:0≤∣s∣≤1000≤∣s∣≤100,保证计算结果始终在整型范围内要求:空间复杂度: O(n)O(n),时间复杂度 O(n)O(n)示例1输入:"1+2"返回值:3示例2输入:"(2*(3-4))*5"返回值:-10示例3输入:"3+2*3*4-1"返回值:26一、使......
  • [ABC132F] Small Products 题解
    题意一句话题意不用再翻译了吧。思路先考虑朴素的dp,设\(dp_{i,j}\)表示长度为\(i\)结尾数字为\(j\)的序列的方案数,状态很好转移:\[dp_{i,j}=\sum_{a=1}^{\lfloor\frac{N}{j}\rfloor}dp_{i-1,a}\]这样时间复杂度是\(\Theta(nk)\)的,显然过不了。考虑优化这个dp。......
  • [ABC156E] Roaming 题解
    前言这哪有蓝,评分似乎有点过了。思路既然是要统计每个房间人数的排列,那我们就枚举把所有人都放到\(i\)个房间里的方案数,这个用插板法解决,把所有人都放到\(i\)个房间里也就是把他们分成\(i\)份,这一部分的答案就是在\(n\)个人的\(n-1\)个空中插入\(i-1\)块隔板的方案......