【2018/07/08测试T3】【SOJ 1836】好数

【题目】

题目描述:

我们定义一个非负整数是“好数”,当且仅当它符合以下条件之一:

1. 这个数是 0 或 1。
2. 所有小于这个数且与它互质的正整数可以排成一个等差数列,例如,8 就是一个好数,因为 1,3,5,7 排成了等差数列。

给出 N 个非负整数,然后进行如下三个操作:

1. 询问区间 [ L , R ] 有多少个好数。
2. 将区间 [ L , R ] 内所有数对 S 取余(S ≤ 1000000)。
3. 将第 C 个数更改为 X 。

提示:如果你不知道如何判断一个数是否为好数,你可以打个表找找规律。

输入格式:

第一行包含两个正整数 N 和 M ,M 表示操作数目。
第二行包含 N 个非负整数。
接下来的 M 行每行表示 1 个操作:“1 L R”表示第 1 个操作,“2 L R S”表示第 2 个操作,“3 C X”表示第 3 个操作。

输出格式:

对每个操作1,输出一个非负整数,表示区间内好数的个数。

样例数据:

【样例1】

输入

3 6
4 6 9
1 1 3
1 3 3
2 1 1 10
1 1 3
3 2 4
1 1 3

输出

2
0
2
2

【样例2】 

输入

8 5
12 24 17 31 16 21 18 30
1 2 5
2 4 7 7
3 2 13
1 1 8
1 3 6

输出

3
6
4

备注:

数据规模与约定:

 

【分析】

首先我们分析一下哪些数是好数,我是先打了一个表(100以内):

0 1 3 4 5 6 7 8 11 13 16 17 19 23 29 31 32 37 41 43 47 53 59 61 64 67 71 73 79 83 89 97 

这样还是不好找规律,我们再重新排一下:

0 1 6
2 4 8 16 32 64
3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97

这样一来,我们发现0、1、6、质数、2的k次幂的数都是好数(证明我不是很会,感性理解一下就行了)

然后我们就可以做题了,通过题意和数据范围我们很容易想到要用线段树

但是对于 2 操作(区间取余),貌似不能用 lazy tag,直接枚举的话又要超时,怎么办呢?

我们直接存一下每个区间的最大值,如果这个值比模数还小,那么就直接跳过这个区间

然后直接套线段树模板就可以了,虽说这个算法还是比较暴力,但还是能过的

(还有一个性质:一个数取余后要么不变化,要么小于它的1/2,听说它有用但我好像没用到)

 

【代码】

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=100005,M=1000005;
int n,m;
int a[N],sum[4*N],maxn[4*N],prime[M];
bool mark[M];
void init()
{
	int i,j,sum=0;
	memset(mark,true,sizeof(mark));
	for(i=2;i<=M;++i)
	{
		if(mark[i])  prime[++sum]=i;
		for(j=1;j<=sum&&i*prime[j]<=M;++j)
		{
			mark[i*prime[j]]=false;
			if(i%prime[j]==0)  break;
		}
	}
	mark[6]=true;
	for(i=4;i<=M;i<<=1)
	  mark[i]=true;
}
void build(int k,int l,int r)
{
	if(l==r)
	{
		sum[k]=mark[a[l]];
		maxn[k]=a[l];
		return;
	}
	int mid=(l+r)>>1;
	build(k<<1,l,mid);
	build(k<<1|1,mid+1,r);
	sum[k]=sum[k<<1]+sum[k<<1|1];
	maxn[k]=max(maxn[k<<1],maxn[k<<1|1]);
}
int query(int k,int l,int r,int x,int y)
{
	if(l>=x&&r<=y)  return sum[k];
	int s=0,mid=(l+r)>>1;
	if(x<=mid)  s+=query(k<<1,l,mid,x,y);
	if(y>mid)  s+=query(k<<1|1,mid+1,r,x,y);
	return s;
}
void mod(int k,int l,int r,int x,int y,int s)
{
	if(maxn[k]<s)  return;
	if(l==r)
	{
		maxn[k]%=s;
		sum[k]=mark[maxn[k]];
		return;
	}
	int mid=(l+r)>>1;
	if(x<=mid)  mod(k<<1,l,mid,x,y,s);
	if(y>mid)  mod(k<<1|1,mid+1,r,x,y,s);
	sum[k]=sum[k<<1]+sum[k<<1|1];
	maxn[k]=max(maxn[k<<1],maxn[k<<1|1]);
}
void change(int k,int l,int r,int x,int y)
{
	if(l==r)
	{
		sum[k]=mark[y];
		maxn[k]=y;
		return;
	}
	int mid=(l+r)>>1;
	if(x<=mid)  change(k<<1,l,mid,x,y);
	else  change(k<<1|1,mid+1,r,x,y);
	sum[k]=sum[k<<1]+sum[k<<1|1];
	maxn[k]=max(maxn[k<<1],maxn[k<<1|1]);
}
int main()
{
//	freopen("good.in","r",stdin);
//	freopen("good.out","w",stdout);
	int x,y,i,p,s;
	scanf("%d%d",&n,&m);
	for(i=1;i<=n;++i)
	  scanf("%d",&a[i]);
	init();
	build(1,1,n);
	for(i=1;i<=m;++i)
	{
		scanf("%d",&p);
		if(p==1)
		{
			scanf("%d%d",&x,&y);
			printf("%d\n",query(1,1,n,x,y));
		}
		if(p==2)
		{
			scanf("%d%d%d",&x,&y,&s);
			mod(1,1,n,x,y,s);
		}
		if(p==3)
		{
			scanf("%d%d",&x,&y);
			change(1,1,n,x,y);
		}
	}
//	fclose(stdin);
//	fclose(stdout);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值