【树状数组阶段性检测】题解

内有小量私货,慎重食用

全是 WATER 你还上不了400???

蒟蒻之疑惑

T1 Count

考点:

我是真的不知道这能有神马考点?

你甚至不需要离散化

每个数均不超过 1500000000 ( 1.5 × 1 0 9 ) 1500000000(1.5\times10^9) 1500000000(1.5×109)

自然,桶排是不行的,考虑快排

将原数组进行排序后,我们依次遍历,如果 a [   i   ] = a [   i + 1   ] a[\ i\ ]=a[\ i+1\ ] a[ i ]=a[ i+1 ] ,就说明当前的元素没有统计完, s u m sum sum 自增,否则,说明当前元素已经统计完了,输出对应的值,并将 s u m sum sum 清空

#include<cstdio>
#include<algorithm>
using namespace std;
int a[200005];
int read(){
	int x=1,b=0;
	char ch=getchar();
	while(!(ch>='0'&&ch<='9')){
		if(ch=='-'){
			x=-1;
		}
		ch=getchar();
	}
	while(ch>='0'&&ch<='9'){
		b=b*10+ch-'0';
		ch=getchar();
	}
	return x*b;
}
void write(int num){
	if(num>=10){
		write(num/10);
	}
	putchar(num%10+'0');
}			//考虑输入输出较大,采用快读快写
int main(){
	int n,ans=1;			//由于开始统计时,至少都有1个元素,所以初始值为1
	n=read();
	for(int i=1;i<=n;i++){
		a[i]=read();
	}
	sort(a+1,a+1+n);
	for(int i=1;i<=n;i++){
		if(a[i]!=a[i+1]){			//当前元素统计完成
			write(a[i]);
			putchar(' ');
			write(ans);
			putchar('\n');			//输出
			ans=1;
		}else{
			ans++;			//同上
		}
	}
	return 0;
}

T2 Friend

考点:埃氏筛

真的,你甚至不需要欧拉筛

其实就是将总的元素数量减去质数的元素数量即可

#include<cstdio>
bool flag[2000005];
void build(int n){			//埃氏筛模板
	for(int i=2;i<=n;i++){
		if(flag[i]==0){
			for(int j=i+i;j<=n;j+=i){
				flag[j]=1;
			}
		}
	}
}
int main(){
	int n,ans=1;			//因为 1 很特殊,所以特殊处理
	scanf("%d",&n);
	build(n);
	for(int i=2;i<=n;i++){			// 1 特殊处理,循环从2开始
		if(flag[i]){			//不是质数
			ans++;
		}
	}
	printf("%d",ans);
	return 0;
}

T3 Self-Number

考点:暴力

我的做法和郭老师的做法不太一样…

当我们枚举到每一个 i i i 时,我们把以 i i i 作为发生器的数标记一下,然后再跑一遍循环,把所有没有标记的数塞进答案数组,最后输出——cqbzgm

下面是蒟蒻的接近于打表的做法:

考虑一位数 a ‾ \overline{a} a ,以 a ‾ \overline{a} a 作为发生器的数为 a + a = 2 a a+a=2a a+a=2a

考虑两位数 a b ‾ \overline{ab} ab ,以 a b ‾ \overline{ab} ab 作为发生器的数为 10 a + b + a + b = 11 b + 2 b 10a+b+a+b=11b+2b 10a+b+a+b=11b+2b

考虑三位数 a b c ‾ \overline{abc} abc ,以 a b c ‾ \overline{abc} abc 作为发生器的数为 100 a + 10 b + c + a + b + c = 101 a + 11 b + 2 c 100a+10b+c+a+b+c=101a+11b+2c 100a+10b+c+a+b+c=101a+11b+2c

由此,我们想到:

枚举每一个数位(因为 1 ≤ N ≤ 1 0 7 1\le N\le10^7 1N107 ,所以最多枚举 7 7 7 位),如果以该书为发生器所得到的数在 N N N 的范围以内,我们将其进行标记

最后,把所有没有标记的数塞进答案数组并输出

#include<cstdio>
bool flag[10000005];
int ans[10000005],cnt;
void build(int num){
	for(int a1=1;a1<=9;a1++){
		for(int a2=0;a2<=9;a2++){
			for(int a3=0;a3<=9;a3++){
				for(int a4=0;a4<=9;a4++){
					for(int a5=0;a5<=9;a5++){
						for(int a6=0;a6<=9;a6++){
							for(int a7=0;a7<=9;a7++){			//枚举7个数位
								if(a1*2<=num){			//处理1位数
									flag[a1*2]=1;
								}
								if(a1*11+a2*2<=num){			//处理2位数
									flag[a1*11+a2*2]=1;
								}
								if(a1*101+a2*11+a3*2<=num){			//处理3位数
									flag[a1*101+a2*11+a3*2]=1;
								}
								if(a1*1001+a2*101+a3*11+a4*2<=num){			//处理4位数
									flag[a1*1001+a2*101+a3*11+a4*2]=1;
								}
								if(a1*10001+a2*1001+a3*101+a4*11+a5*2<=num){			//处理5位数
									flag[a1*10001+a2*1001+a3*101+a4*11+a5*2]=1;
								}
								if(a1*100001+a2*10001+a3*1001+a4*101+a5*11+a6*2<=num){			//处理6位数
									flag[a1*100001+a2*10001+a3*1001+a4*101+a5*11+a6*2]=1;
								}
								if(a1*1000001+a2*100001+a3*10001+a4*1001+a5*101+a6*11+a7*2<=num){			//处理7位数
									flag[a1*1000001+a2*100001+a3*10001+a4*1001+a5*101+a6*11+a7*2]=1;
								}
							}
						}
					}
				}
			}
		}
	}
}
int read(){
	int x=1,b=0;
	char ch=getchar();
	while(!(ch>='0'&&ch<='9')){
		if(ch=='-'){
			x=-1;
		}
		ch=getchar();
	}
	while(ch>='0'&&ch<='9'){
		b=b*10+ch-'0';
		ch=getchar();
	}
	return x*b;
}
void write(int num){
	if(num>=10){
		write(num/10);
	}
	putchar(num%10+'0');
}			//快读快写
int main(){
	int n,m,x;
	n=read(),m=read();
	build(n);			//标记自身数和非自身数
	for(int i=1;i<=n;i++){
		if(flag[i]==0){			//自身数
			ans[++cnt]=i;			//塞入答案中
		}
	}
	write(cnt);
	putchar('\n');
	for(int i=1;i<=m;i++){
		x=read();
		write(ans[x]);			//输出
		putchar(' ');
	}
	return 0;
}

T4 [CQOI2006]简单题

蒟蒻来教大家手切省选啦

考点:树状数组

就是一个树状数组板题,只需要最后输出时模 2 2 2 即可

#include<cstdio>
int Bit[100005],n,m,x,y;
int Low_Bit(int num){
	return num&-num;
}
void Up_Date(int num,int sum){
	for(int i=num;i<=n;i+=Low_Bit(i)){
		Bit[i]+=sum;
	}
}
long long int Sum(int num){
	long long int ans=0;
	for(int i=num;i>=1;i-=Low_Bit(i)){
		ans+=Bit[i];
	}
	return ans;
}			//树状数组模板
int read(){
	int a=1,b=0;
	char ch=getchar();
	while(!(ch>='0'&&ch<='9')){
		if(ch=='-'){
			a=(~a)+1;
		}
		ch=getchar();
	}
	while(ch>='0'&&ch<='9'){
		b=(b<<1)+(b<<3)+ch-'0';
		ch=getchar();
	}
	return a*b;
}			//快读
int main(){
	n=read(),m=read();
	for(int i=1;i<=m;i++){
		x=read();
		if(x==1){
			x=read(),y=read();
			Up_Date(x,1);
			Up_Date(y+1,-1);			//区间修改,单点查询模板
		}else{
			x=read();
			printf("%lld\n",Sum(x)%2);			//结果模 2 即可
		}
	}
	return 0;
}

T5 火神之友

巴巴托斯(某知名游戏的风神),干点正事吧

考点:并查集

我们考虑如下图所示的情况:

在这里插入图片描述

我们尝试从左到右进行枚举,当枚举到第 2 2 2 位的时候,刚好出现了两个 3 3 3 ,此时,我们需要进行答案的累加,及 U p D a t e ( 1 , 1 ) UpDate(1,1) UpDate(1,1)

在这里插入图片描述

当枚举到第 3 3 3 位的时候,考虑区间 [   2 , 3   ] [\ 2,3\ ] [ 2,3 ] ,是满足要求的,进行 U p D a t e ( 2 , 1 ) UpDate(2,1) UpDate(2,1)

然而,考虑区间 [   1 , 3   ] [\ 1,3\ ] [ 1,3 ] ,出现了 3 3 3 3 3 3 ,是不满足要求的,又因为我们已经加了两个 1 1 1 了,所以,我们在第 1 1 1 位进行 − 2 -2 2 操作即可抵消两个 + 1 +1 +1 ,即 U p D a t e ( 1 , − 2 ) UpDate(1,-2) UpDate(1,2)

在这里插入图片描述

当枚举到第 3 3 3 位的时候,我们可以依葫芦画瓢,进行 U p D a t e ( 3 , 1 ) UpDate(3,1) UpDate(3,1) U p D a t e ( 2 , − 2 ) UpDate(2,-2) UpDate(2,2) 操作

但是,考虑区间 [   1 , 4   ] [\ 1,4\ ] [ 1,4 ] ,我们又多减了 1 1 1 ,怎么办?加回来就行了(真的很潦草,但就是正解),即 U p D a t e ( 1 , 1 ) UpDate(1,1) UpDate(1,1)

就这么:我们可以得到一般性的规律:对于第 i i i 号位,我们需要进行如下操作: U p D a t e ( i − 1 , 1 ) ( 1 < i ) , U p D a t e ( i − 2 , − 2 ) ( 2 < i ) , U p D a t e ( i − 3 , 1 ) ( 3 < i ) UpDate(i-1,1)(1<i),UpDate(i-2,-2)(2<i),UpDate(i-3,1)(3<i) UpDate(i1,1)(1<i),UpDate(i2,2)(2<i),UpDate(i3,1)(3<i)

对于每一个区间,我们也需要注意:

我们需要按照每一个区间右端点从小到大进行排序,再依次处理(即先处理靠右的区间)

原因很简单,因为我们在处理靠左的区间的时候,可能会多出一些 -2 和 +1 操作,然而,对于靠右的区间来说,这些操作可能会对其正确结果产生干扰,所以,要先处理靠右的区间

另外,因为靠后的操作会对靠右的区间造成干扰,所以要边处理边记录答案

BUT!

以上所讲都是元素相同的情况,那不同的情况呢?(不同情况自然白给

我们可以将所有的相同元素全部塞入一条链中进行处理,这样,不同的元素之间就互不干扰,在进行答案累加时,所有答案又能加在一起

下面是完整代码:

#include<map>
#include<cstdio>
#include<algorithm>
using namespace std;
int a[500005],n,m,x,y;
int Bit[500005],last[500005];
map<int,int> ma;			//用来记录每一个元素是否出现过以及上一个对应元素的下标
struct node{
	int l,r,id;
}q[500005];			//记录每一个问题区间
int ans[500005];
int Low_Bit(int num){
	return num&-num;
}
void Up_Date(int num,int sum){
	for(int i=num;i<=n;i+=Low_Bit(i)){
		Bit[i]+=sum;
	}
}
int Sum(int num){
	int ans=0;
	for(int i=num;i>=1;i-=Low_Bit(i)){
		ans+=Bit[i];
	}
	return ans;
}			//树状数组模板
bool cmp(node a,node b){
	return a.r<b.r;
}			//右端点排序
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);
		if(ma.find(a[i])!=ma.end()){			//该元素出现过
			last[i]=ma[a[i]];			//将前驱对应下标进行赋值
		}else{
			last[i]=-1;			//第一个,没有前驱
		}
		ma[a[i]]=i;			//更新该元素前驱下标
	}
	for(int i=1;i<=m;i++){
		scanf("%d%d",&q[i].l,&q[i].r);			//输入区间
		q[i].id=i;
	}
	sort(q+1,q+1+m,cmp);
	int tot=1;
	for(int i=1;i<=m;i++){
		while(tot<=q[i].r){			//在完成一个区间处理前一直处理
			if(last[tot]!=-1){
				Up_Date(last[tot],1);
				if(last[last[tot]]!=-1){
					Up_Date(last[last[tot]],-2);
					if(last[last[last[tot]]]!=-1){
						Up_Date(last[last[last[tot]]],1);
					}
				}
			}			//对应处理,上文已提
			tot++;
		}
		ans[q[i].id]=Sum(q[i].r)-Sum(q[i].l-1);			//答案赋值
	}
	for(int i=1;i<=m;i++){
		printf("%d\n",ans[i]);			//输出
	}
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值