zoukankan      html  css  js  c++  java
  • 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.


    题解Here!

    本来想用线段树维护一下,然后发现我好像需要$3$个$log_2n$,这是要爆炸的节奏啊。。。

    所以我们拿出了分块。

    一开始我还在想,$GCD$和$XOR$有什么联系,然后发现,这不需要联系啊。。。

    首先,$GCD$有个还算不错的性质:

    对于一列数组,从左往右取前缀$gcd$,不同的值最多只有$log_2n$种。

    并且每次值如果改变,那么前缀$gcd$的值至少除以二。

    对于每个块,维护下列信息:

    块内数据$xor$和,块内$gcd$,块的头尾两个数的前缀$gcd$,块内每个数以块左端点为头的前缀$xor$和。

    对于第四类信息,还需要用某种方法,使得支持在$log_2n$的时间内询问是否存在一个数。

    修改的时候,修改位置所在块暴力重构,后面的块更新第三类信息即可。

    查询的时候,如果某个块的第三类信息相等,说明这个块内前缀$gcd$都不变,有没有解查表就知道了。

    这个表怎么搞呢?

    假设暴力扫描,如果前面的块所取到的前缀$gcd$为$lastgcd$,$xor$为$lastxor$。

    若$gcd(lastgcd,Gcd[r[i]])==lastgcd$,则说明这个块内所有的数取$gcd$后都是$lastgcd$,那么$xor[j]=(frac{x}{lastgcd}quad xor quad lastxor)$。

    然后在另一个排好序的数组中二分查找就可以了。

    否则,这个块内暴力访问看一下是否有解,因为不同的$gcd$值不超过$log_2n$种,所以暴力访问次数并不多。

    所以复杂度就是$O(nsqrt nlog_2n)$。

    附代码:

    #include<iostream>
    #include<algorithm>
    #include<cstdio>
    #include<cmath>
    #define MAXN 100010
    using namespace std;
    int n,m,block;
    int colour[MAXN],Left[MAXN],Right[MAXN];
    long long val[MAXN],gcd_sum[MAXN],xor_sum[MAXN];
    struct node{
    	long long x;
    	int id;
    	friend bool operator <(const node &p,const node &q){
    		if(p.x==q.x)return p.id<q.id;
    		return p.x<q.x;
    	}
    }a[MAXN];
    inline long long read(){
    	long long date=0,w=1;char c=0;
    	while(c<'0'||c>'9'){if(c=='-')w=-1;c=getchar();}
    	while(c>='0'&&c<='9'){date=date*10+c-'0';c=getchar();}
    	return date*w;
    }
    long long gcd(long long x,long long y){
    	if(!y)return x;
    	return gcd(y,x%y);
    }
    int half_find(int l,int r,long long x){
    	int mid,ans=l;
    	while(l<=r){
    		mid=l+r>>1;
    		if(a[mid].x>=x){ans=mid;r=mid-1;}
    		else l=mid+1;
    	}
    	return ans;
    }
    void build(int x){
    	gcd_sum[Left[x]]=xor_sum[Left[x]]=val[Left[x]];
    	a[Left[x]]=(node){val[Left[x]],Left[x]};
    	for(int i=Left[x]+1;i<=Right[x];i++){
    		gcd_sum[i]=gcd(gcd_sum[i-1],val[i]);
    		xor_sum[i]=xor_sum[i-1]^val[i];
    		a[i]=(node){xor_sum[i],i};
    	}
    	sort(a+Left[x],a+Right[x]+1);
    }
    int solve(long long x){
    	int ans=-1;
    	long long Gcd=val[1],Xor=0;
    	for(int i=1;i<=colour[n]&&ans==-1;i++){
    		if(gcd(Gcd,gcd_sum[Right[i]])==Gcd){
    			if(x%Gcd==0){
    				long long k=(x/Gcd)^Xor;
    				int pos=half_find(Left[i],Right[i],k);
    				if(a[pos].x==k){
    					ans=a[pos].id;
    					break;
    				}
    			}
    			Gcd=gcd(Gcd,gcd_sum[Right[i]]);Xor^=xor_sum[Right[i]];
    		}
    		else{
    			for(int j=Left[i];j<=Right[i];j++){
    				Gcd=gcd(Gcd,val[j]);Xor^=val[j];
    				if(Gcd*Xor==x){
    					ans=j;
    					break;
    				}
    			}
    			if(ans!=-1)break;
    		}
    	}
    	return ans;
    }
    void work(){
    	char ch[10];
    	long long x,y;
    	while(m--){
    		scanf("%s",ch);x=read();
    		if(ch[0]=='M'){
    			x++;y=read();
    			val[x]=y;
    			build(colour[x]);
    		}
    		else{
    			int s=solve(x);
    			if(s==-1)printf("no
    ");
    			else printf("%d
    ",s-1);
    		}
    	}
    }
    void init(){
    	n=read();
    	block=(int)sqrt(n);
    	for(int i=1;i<=n;i++){
    		colour[i]=(i-1)/block+1;
    		if(!Left[colour[i]])Left[colour[i]]=i;
    		Right[colour[i]]=i;
    		val[i]=read();
    	}
    	for(int i=1;i<=colour[n];i++)build(i);
    	m=read();
    }
    int main(){
    	init();
    	work();
        return 0;
    }
    
  • 相关阅读:
    .net core 学习小结之 配置介绍(config)以及热更新
    .net core 学习小结之环境配置篇
    powershell下载网站图片
    Powershell 脚本输出前十条消耗内存的进程到excel
    Linux 自学shell
    使用bat脚本进行开机启动批处理
    Git 创建分支并合并主分支
    Git 的使用及其一些基本用法
    点击按钮复制文本到剪切板
    关于一些基本排序的实现
  • 原文地址:https://www.cnblogs.com/Yangrui-Blog/p/9538578.html
Copyright © 2011-2022 走看看