Luogu P4108 [HEOI2015]公约数数列

Luogu P4108(分块)

Luogu P4108

观察题目要求,修改操作为单点修改,而查询为前缀查询,再注意到 \(q\) 的大小,(还有看标签) ,知道这道题可以用分块进行解决。

首先是关于修改,题目要求单点修改,将区间分块后,单点修改可以在块内暴力修改,时间复杂度 \(O(\sqrt{n})\)

然后就是如何查询的问题。对于 \(GCD\) ,可以用 \(O(logn)\) 的方式求出,而 \(XOR\) 也可以维护一个前缀异或和。但是问题是该如何查询?显然不能逐个枚举,这样时间复杂度就再次退化成为了 \(O(n)\) 。这时候分块就派上了用场。

我们维护好每个块中的前缀 \(GCD\)前缀 \(XOR\)。由于 \(GCD\) 的特殊性质,可以得知 \(GCD\) 的所有可能取值最大只有 \(logn\) 个,换言之,就是有很多块满足 \(GCD[1]==GCD[n]\) (这里的 \(1\) 表示当前块的第一个,\(n\) 表示最后一个)。对于这些块,只需要提前存储当前块中出现过的 \(XOR\) 值,便于知道查询的值 \(x\) 后,快速确定 \(\frac{x}{GCD[1]}\) 这样一个值是否在当前块含有的 \(XOR\) 值中出现过,同时获取位置,可以用 \(\text{unordered\_map}\) 实现(这玩意是由 \(\text{Hash}\) 表维护的,因此修改和查询时间复杂度都可视作 \(O(1)\))。

修改操作也很简单,获取当前下标所在块编号,然后直接在当前块中扫一遍,暴力维护前缀 \(GCD\)前缀 \(XOR\)以及 \(\text{unordered\_map}\)

#include<cstdio>
#include<unordered_map>
#include<cmath>
#include<algorithm>
#include<cstdlib>
#define LL long long
#define mem(a,b) memset(a,b,sizeof(a));
#define map unordered_map
using namespace std;
template<typename T> inline void read(T &k)
{
	k=0;
	T flag=1;char b=getchar();
	while (b<'0' || b>'9') {flag=(b=='-')?-1:1;b=getchar();}
	while (b>='0' && b<='9') {k=(k<<3)+(k<<1)+(b^48);b=getchar();}
	k*=flag;
}
const int _SIZE=1e5;
int n,q;
int a[_SIZE+5];
map<int,int> XORPos[_SIZE+5];//记录XOR值在每个块中出现的位置,这里的map被定义为了unordered_map
int L[_SIZE+5],R[_SIZE+5];//每个块的左右断点
int len,cnt,pos[_SIZE+5];//块长,块个数,每个点所在块编号
int XOR[_SIZE+5],GCD[_SIZE+5];//前缀XOR值,前缀GCD
inline int gcd(int x,int y) {return __gcd(x,y);}//计算gcd
void PreWork()//预处理
{
	len=sqrt(n);//块长为n^(1/2)
	cnt=(n-1)/len+1;//块的个数
	for (int i=1;i<=cnt;i++)//处理每一个块
	{
		L[i]=(i-1)*len+1;//左端点
		R[i]=min(i*len,n);//右端点
		pos[L[i]]=i;//将块的左端点特别赋值,因为它是当前块的开头
		GCD[L[i]]=a[L[i]];
		XOR[L[i]]=a[L[i]];
		if (XORPos[i].find(XOR[L[i]])==XORPos[i].end())//记得将开头的值放入map中
			XORPos[i][XOR[L[i]]]=L[i];
		for (int j=L[i]+1;j<=R[i];j++)//遍历当前块
		{
			pos[j]=i;//对应块编号
			GCD[j]=gcd(GCD[j-1],a[j]);//前缀GCD
			XOR[j]=XOR[j-1]^a[j];//前缀XOR和
			if (XORPos[i].find(XOR[j])==XORPos[i].end())//插入map
				XORPos[i][XOR[j]]=j;
		}
	}
}
void update(int x,int v)//更新,将位置x更新为v
{
	a[x]=v;//更改值
	int which=pos[x];//获取块编号
	GCD[L[which]]=a[L[which]];//以下与预处理方式相同
	XOR[L[which]]=a[L[which]];
	XORPos[which].clear();
	if (XORPos[which].find(XOR[L[which]])==XORPos[which].end())
		XORPos[which][XOR[L[which]]]=L[which];
	for (int j=L[which]+1;j<=R[which];j++)
	{
		GCD[j]=gcd(GCD[j-1],a[j]);
		XOR[j]=XOR[j-1]^a[j];
		if (XORPos[which].find(XOR[j])==XORPos[which].end())
			XORPos[which][XOR[j]]=j;
	}
}
int query(LL x)//查询值x对应的答案p
{
	int gcdtemp=a[1],temp=0;//记录1~j的前缀GCD与前缀XOR和
	for (int i=1;i<=cnt;i++)//以块为单位
	{
		if (gcd(gcdtemp,GCD[R[i]])==gcdtemp)//如果当前块的GCD全部相同
		{
			if (x%(gcdtemp)==0 && XORPos[i].find(x/gcdtemp^temp)!=XORPos[i].end())
				return XORPos[i][x/gcdtemp^temp];//查询有没有符合答案的XOR值,有就返回
			else
			{
				temp^=XOR[R[i]];//将块的XOR和全部加在temp上
				gcdtemp=gcd(gcdtemp,GCD[R[i]]);//更新GCD
				continue;//该块处理完成
			}
		}
		for (int j=L[i];j<=R[i];j++)//如果GCD值不相等
		{
			gcdtemp=gcd(gcdtemp,a[j]);
			temp^=a[j];//更新1~j的gcdtemp与temp
			if ((LL)gcdtemp*temp==x) return j;//如果符合答案要求即返回,记得long long
		}
	}
	return -1;//找完所有块也没有答案,返回一个-1
}
int main()
{
	read(n);
	for (int i=1;i<=n;i++) read(a[i]);
	PreWork();
	read(q);
	char com[25];
	int pos,ans;LL val;
	for (int i=1;i<=q;i++)
	{
		scanf("%s",com);
		if (com[0]=='M')//修改
		{
			read(pos);read(val);
			pos++;
			update(pos,val);
		}
		else//查询
		{
			read(val);
			ans=query(val);
			if (ans!=-1) printf("%d\n",ans-1);
			else puts("no");//返回值为-1,即没有答案
		}
	}
	return 0;
}
posted @ 2022-07-06 09:24  Hanx16Msgr  阅读(8)  评论(0编辑  收藏  举报