Luogu P4108 [HEOI2015]公约数数列
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;
}