【BZOJ4028】[HEOI2015]公约数数列 分块

【BZOJ4028】[HEOI2015]公约数数列

Description

设计一个数据结构. 给定一个正整数数列 a_0, a_1, ..., a_{n - 1},你需要支持以下两种操作:

1. MODIFY id x: 将 a_{id} 修改为 x.

2. QUERY x: 求最小的整数 p (0 <= p < n),使得 gcd(a_0, a_1, ..., a_p) * XOR(a_0, a_1, ..., a_p) = x. 其中 XOR(a_0, a_1, ..., a_p) 代表 a_0, a_1, ..., a_p 的异或和,gcd表示最大公约数。

Input

输入数据的第一行包含一个正整数 n.

接下来一行包含 n 个正整数 a_0, a_1, ..., a_{n - 1}.

之后一行包含一个正整数 q,表示询问的个数。

之后 q 行,每行包含一个询问。格式如题目中所述。

Output

对于每个 QUERY 询问,在单独的一行中输出结果。如果不存在这样的 p,输出 no.

Sample Input

10
1353600 5821200 10752000 1670400 3729600 6844320 12544000 117600 59400 640
10
MODIFY 7 20321280
QUERY 162343680
QUERY 1832232960000
MODIFY 0 92160
QUERY 1234567
QUERY 3989856000
QUERY 833018560
MODIFY 3 8600
MODIFY 5 5306112
QUERY 148900352

Sample Output

6
0
no
2
8
8

HINT

对于 100% 的数据,n <= 100000,q <= 10000,a_i <= 10^9 (0 <= i < n),QUERY x 中的 x <= 10^18,MODIFY id x 中的 0 <= id < n,1 <= x <= 10^9.

题解:做过magical GCD那题之后就知道这个套路了。一个序列的本质不同的前缀gcd只有log个(因为从左往右,gcd每次变化时都至少减半)。所以可以暴力维护每个gcd变化的位置。

然后问题变成了问你一个区间中是否存在一个位置x,使得1到x的异或和=val。考虑分块,块内排序。修改时小块暴力,大块打标记;查询时在块内二分查找有没有sum=val^tag的即可。

#include <cstdio>
#include <cstring>
#include <iostream>
#include <cmath>
#include <algorithm>
using namespace std;
const int maxn=100010;
typedef long long ll;
int n,m,B,gn;
int tag[1000],v[maxn],s[maxn],sp[maxn],g[100],gp[100],bg[1000];
char str[20];
inline ll rd()
{
	ll ret=0,f=1;	char gc=getchar();
	while(gc<‘0‘||gc>‘9‘)	{if(gc==‘-‘)f=-f;	gc=getchar();}
	while(gc>=‘0‘&&gc<=‘9‘)	ret=ret*10+gc-‘0‘,gc=getchar();
	return ret*f;
}
int gcd(int a,int b)
{
	return !b?a:gcd(b,a%b);
}
inline bool eql(ll a,ll b)
{
	return (a==b)||((a)&&(b%a==0));
}
bool cmp(const int &a,const int &b)
{
	return (s[a]==s[b])?(a<b):(s[a]<s[b]);
}
void getnxt()
{
	int a=gp[gn]+1,c=a/B,i,j;
	gn++,g[gn]=gcd(g[gn-1],v[a]);
	for(j=a;j<c*B+B&&j<n;j++)	if(!eql(g[gn],v[j]))	{gp[gn]=j-1;	return ;}
	if(j==n)	{gp[gn]=n-1;	return ;}
	for(i=c+1;i*B<n;i++)	if(!eql(g[gn],bg[i]))	break;
	if(i*B>=n)	{gp[gn]=n-1;	return ;}
	for(j=i*B;j<i*B+B&&j<n;j++)	if(!eql(g[gn],v[j]))	{gp[gn]=j-1;	return ;}
	gp[gn]=n-1;	return ;
}
bool query(int x,ll val)
{
	if(!eql(g[x],val))	return 0;
	val=(!g[x])?val:val/g[x];
	int a=gp[x-1]+1,b=gp[x],c=a/B,d=b/B,j;
	if(c==d)
	{
		for(j=a;j<=b;j++)	if((s[j]^tag[c])==val)	{printf("%d\n",j);	return 1;}
		return 0;
	}
	for(j=a;j<c*B+B;j++)	if((s[j]^tag[c])==val)	{printf("%d\n",j);	return 1;}
	for(j=c+1;j<d;j++)
	{
		int l=j*B,r=j*B+B,mid;
		while(l<r)
		{
			mid=(l+r)>>1;
			if(s[sp[mid]]>=(val^tag[j]))	r=mid;
			else	l=mid+1;
		}
		if(r<j*B+B&&s[sp[r]]==(val^tag[j]))	{printf("%d\n",sp[r]);	return 1;}
	}
	for(j=d*B;j<=b;j++)	if((s[j]^tag[d])==val)	{printf("%d\n",j);	return 1;}
	return 0;
}
int main()
{
	n=rd(),B=int(sqrt(double(n)));
	int i,j,a,c;
	ll b,d;
	for(i=0;i<n;i++)	v[i]=rd();
	gp[0]=-1,g[gn=1]=v[0];
	for(i=0;i<n;i++)
	{
		if(i%B==0)	tag[i/B+1]=tag[i/B],s[i]=v[i];
		else	s[i]=s[i-1]^v[i];
		tag[i/B+1]^=v[i],bg[i/B]=gcd(v[i],bg[i/B]),sp[i]=i;
		if(!eql(g[gn],v[i]))	g[gn+1]=gcd(v[i],g[gn]),gp[gn]=i-1,gn++;
	}
	gp[gn]=n-1;
	for(i=0;i<n;i+=B)	sort(sp+i,sp+min(i+B,n),cmp);
	m=rd();
	for(i=1;i<=m;i++)
	{
		scanf("%s",str);
		if(str[0]==‘M‘)
		{
			a=rd(),b=rd(),c=a/B,d=v[a],v[a]=b,bg[c]=0;
			for(j=c*B;j<min(c*B+B,n);j++)	s[j]=(j==c*B)?v[j]:s[j-1]^v[j],sp[j]=j,bg[c]=gcd(bg[c],v[j]);
			sort(sp+c*B,sp+min(c*B+B,n),cmp);
			for(j=c+1;j*B<n;j++)	tag[j]^=(b^d);
			gn=0;
			while(gp[gn]<n-1)	getnxt();
		}
		else
		{
			b=rd();
			for(j=1;j<=gn&&!query(j,b);j++);
			if(j==gn+1)	printf("no\n");
		}
	}
	return 0;
}
时间: 2024-11-12 21:22:46

【BZOJ4028】[HEOI2015]公约数数列 分块的相关文章

bzoj4028 [HEOI2015]公约数数列(分块+卡常?)

被卡常卡到怀疑人生. 思维又难又卡常(可能是我写的太丑了)心态炸了. 最后还是照题解打的.(题解多了一个排序,似乎快了很多) 所以代码就不发了... 原文地址:https://www.cnblogs.com/Xu-daxia/p/9465265.html

bzoj4028: [HEOI2015]公约数数列

Description 设计一个数据结构. 给定一个正整数数列 a_0, a_1, ..., a_{n - 1},你需要支持以下两种操作: 1. MODIFY id x: 将 a_{id} 修改为 x. 2. QUERY x: 求最小的整数 p (0 <= p < n),使得 gcd(a_0, a_1, ..., a_p) * XOR(a_0, a_1, ..., a_p) = x. 其中 XOR(a_0, a_1, ..., a_p) 代表 a_0, a_1, ..., a_p 的异或和,g

4028: [HEOI2015]公约数数列

4028: [HEOI2015]公约数数列 Description 设计一个数据结构. 给定一个正整数数列 a_0, a_1, ..., a_{n - 1},你需要支持以下两种操作: 1. MODIFY id x: 将 a_{id} 修改为 x. 2. QUERY x: 求最小的整数 p (0 <= p < n),使得 gcd(a_0, a_1, ..., a_p) * XOR(a_0, a_1, ..., a_p) = x. 其中 XOR(a_0, a_1, ..., a_p) 代表 a_0

[HEOI2015]公约数数列

公约数数列 [问题描述] 设计一个数据结构. 给定一个正整数数列 a_0, a_1, ...,a_{n - 1},你需要支持以下两种操作: 1. MODIFY id x: 将 aid 修改为 x. 2. QUERY x: 求最小的整数 p (0 <= p < n),使得 gcd(a_0,a_1, ..., a_p) * XOR(a_0, a_1, ..., a_p) = x. 其中 XOR(a_0, a_1, ..., a_p) 代表 a_0, a_1, ..., a_p 的异或和,gcd表示

bzoj 4028 : [HEOI2015]公约数数列

之前看了好几次都没什么思路,今天下定决心把这题切了. 观察到$0-x$的gcd最多变化log次,因为它每次变化一定至少要去掉一个质因子,所以我们可以枚举gcd. 因为数据范围比较小,所以想到了分块. 设T为块的大小. 维护块首到块里每个位置的gcd和xor,再把xor排序. 修改的时候暴力改就行,复杂度$TlogT$. 询问的时候如果gcd在这个块里变化了,就把这个块暴力扫一遍,否则说明gcd在这个块里不变,相当于在区间里查是否有某个特定的值,随便二分一下,复杂度$T log inf+\frac

算法笔记--数列分块

黄老师的博客http://hzwer.com/8053.html 模板: const int N=1e5+5; int a[N],belong[N]/*属于哪个块*/,blo/*块的大小*/,block[1000]; void update(int l,int r){ if(belong[l]==belong[r]){ for(int i=l;i<=r;i++){ //你的操作 } return ; } for(int i=l;i<=belong[l]*blo;i++){ //你的操作 } f

loj 6278 6279 数列分块入门 2 3

参考:「分块」数列分块入门1 – 9 by hzwer 2 Description 给出一个长为\(n\)的数列,以及\(n\)个操作,操作涉及区间加法,询问区间内小于某个值\(x\)的元素个数. 思路 每个块内保持升序排列. 则块外暴力统计,块内二分查找分界点. 一些注意点,如: 要记录下标: 块外暴力修改完之后需要再排序: 在块内二分查找的值是\(c-tag[i]\)而非\(c\). Code #include <bits/stdc++.h> #define maxn 50010 #def

loj 6277 6280 数列分块入门 1 4

参考:「分块」数列分块入门1 – 9 by hzwer 1 Description 给出一个长为\(n\)的数列,以及\(n\)个操作,操作涉及区间加法,单点查值. 思路 用\(tag\)记录每个块整体的增量. Code #include <bits/stdc++.h> #define maxn 50010 #define F(i, a, b) for (int i = (a); i < (b); ++i) #define F2(i, a, b) for (int i = (a); i

LOJ#6284. 数列分块入门 8

#6284. 数列分块入门 8 内存限制:256 MiB时间限制:500 ms标准输入输出 题目类型:传统评测方式:文本比较 上传者: hzwer 提交提交记录统计讨论 1 测试数据 题目描述 给出一个长为 nnn 的数列,以及 nnn 个操作,操作涉及区间询问等于一个数 ccc 的元素,并将这个区间的所有元素改为 ccc. 输入格式 第一行输入一个数字 nnn. 第二行输入 nnn 个数字,第 i 个数字为 aia_ia?i??,以空格隔开. 接下来输入 nnn 行询问,每行输入三个数字 ll