公约数数列

公约数数列

时间限制: 1 Sec 内存限制: 256 MB

题目描述
设计一个数据结构. 给定一个正整数数列 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表示最大公约数。

输入
输入数据的第一行包含一个正整数 n.
接下来一行包含 n 个正整数 a_0, a_1, …, a_{n - 1}.
之后一行包含一个正整数 q,表示询问的个数。
之后 q 行,每行包含一个询问。格式如题目中所述。

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

样例输入
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

样例输出
6
0
no
2
8
8

【数据规模与约定】
对于 30% 的数据,n <= 10000,q <= 1000.
对于 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.

来源
heoi2015 day1

题解

本来想用线段树写,后来发现线段树写不出来。后来脑补一下发现分块可以过,就码了分块。
这里需要用到一个结论,在一段数中gcd的最多只有logn种。
所以对于每个·块,如果gcd相同,就二分答案。
又知道,sumi^tagi(标记)*gcd(tmp,gcdi)=x,把这个式子划一下,求出sumi的值,用二分,哈希,map之类的办法查找一下区间内是否有这个人数字即可。
对于gcd不同的区间,暴力处理,因为最多只有logn个这样的区间,所以总复杂度为n* sqrt(n) *logn。

代码

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
#define N 100010
#define M 350
#define ll long long
using namespace std;
int n,q,num,s[N],sum[N],blo[N],l[M],r[M],g[M],tag[M];
struct node{
  int num,pos;
  bool operator<(const node &x)
  const{return num<x.num||(num==x.num&&pos<x.pos);}
}t[N];

int gcd(int a,int b)
{
  if(!b)return a;
  return gcd(b,a%b);
}

int main()
{
  int x,y;ll a;char str[10];
  scanf("%d",&n);num=sqrt(n);
  for(int i=1;i<=n;i++)scanf("%d",&s[i]);
  for(int i=1;i<=num;i++)
  {
    l[i]=r[i-1]+1;r[i]=min(l[i]+num,n);
    for(int j=l[i];j<=r[i];j++)
    {
      blo[j]=i;sum[j]=sum[j-1]^s[j];
      g[i]=gcd(g[i],s[j]);t[j]=(node){sum[j],j};
    }
    sort(t+l[i],t+r[i]+1);
  }
  scanf("%d",&q);
  while(q--)
  {
    scanf(" %s",str);
    if(str[0]=='M')
    {
      scanf("%d%d",&x,&y);x++;
      int pos=blo[x];y^=s[x];s[x]^=y;g[pos]=0;
      for(int i=l[pos];i<=r[pos];i++)g[pos]=gcd(g[pos],s[i]);
      for(int i=pos+1;i<=num;i++)tag[i]^=y;
      for(int i=l[pos];i<x;i++)t[i]=(node){sum[i],i};
      for(int i=x;i<=r[pos];i++)sum[i]^=y,t[i]=(node){sum[i],i};
      sort(t+l[pos],t+r[pos]+1);
    }
    else
    {
      int ans=0;
      scanf("%lld",&a);
      for(int i=1,tmp=0;i<=num&&!ans;i++)
      {
        if(gcd(tmp,s[l[i]])==gcd(tmp,g[i]))
        {
          int tot=(a/gcd(tmp,g[i]))^tag[i];
          int tp=lower_bound(t+l[i],t+r[i]+1,(node){tot,0})-t;
          tmp=gcd(tmp,g[i]);
          if(t[tp].num!=tot)continue;
          ans=t[tp].pos;
        }
        else
        {
          if(tag[i])
          {
            for(int j=l[i];j<=r[i];j++)
              sum[j]^=tag[i],t[j]=(node){sum[j],j};
            tag[i]=0;sort(t+l[i],t+r[i]+1);
          }
          for(int j=l[i];j<=r[i];j++)
          {
            if((ll)sum[j]*gcd(tmp,s[j])==a){ans=j;break;}
            tmp=gcd(tmp,s[j]);
          }
        }
      }
      if(!ans)printf("no\n");
      else printf("%d\n",ans-1);
    }
  }
  return 0;
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值