zoukankan      html  css  js  c++  java
  • JZOJ100045 【NOIP2017提高A组模拟7.13】好数

    题目

    在这里插入图片描述

    题目大意

    首先有一个定义:
    对于一个数,如果和它互质的数可以组成一个等差数列,那么这个数叫“好数”。
    现在给你一个数列,有三种操作:
    1、询问一段区间内的好数的个数。
    2、将一段区间内的数分别模一个值。
    3、将某个数修改。


    思考历程

    先看看这个题目。
    好熟悉的题目啊!这不就是初中OJ上的某道数位DP的题吗?
    然后发现不是那一道题,松了一口气。

    一眼看下去,一定有什么数论。说不定在得到了什么结论之后,就变成一个非常简单的数据结构题了。
    然后就在疯狂地推式子……最终没有推出来……
    于是就弃疗了。


    正解

    经过打表找规律后,我们发现:
    好数分为三种情况:
    1、质数。
    2、22的幂次。
    3、66
    随便想一想,我们很容易知道这些数都是好数。
    可问题是,其它的数有没有可能是好数呢?
    我不知道,我也不会证明……
    不过对于这题来说,由于数据范围不大(也就是10610^6以内),所以打个表当然是可以的……
    这再次告诉了我们打表找规律很重要的道理。

    在CGH大佬的讲解下,我终于会证明了……
    首先,每个数都可以表示成2kx2^kx的形式,其中xx为奇数。
    接下来我们分类讨论:
    x=1x=1
    我觉得这个不用说……
    k=0k=0时,
    首先gcd(x1,x)=1gcd(x-1,x)=1
    由于xx为奇数,所以gcd(x2,x)=1gcd(x-2,x)=1
    那么x1x-1x2x-2都与xx互质,所以等差数列的公差为11
    既然这样,11x1x-1都要和xx互质。
    所以xx自然是质数。
    k>0k>0时,
    由于xx为奇数,所以gcd(x2,x)=1  gcd(x+2,x)=1gcd(x-2,x)=1 gcd(x+2,x)=1
    k>0k>0x>1x>1所以x+2<2kxx+2<2^kx
    x1x-1x+1x+1为偶数,和2kx2^kx不互质。显然xx2kx2^kx也不互质。
    所以公差为44
    首项显然是11,然后我们列出来:1 5 9...
    11552kx2^kx互质,没有问题。
    可是99呢?
    如果2kx2^kx99互质,那么2kx2^kx必定和33互质,所以不成立!
    所以在k>0k>0x>1x>1时,可以的取值范围在99以内。特别计算一下,只有66符合条件。
    综上所述,只有0022的幂次、质数、66可以为好数。
    得证。
    再次膜拜一下CGH大爷。

    知道了这个结论之后,仔细地再想一想,其实出解还是很容易的。
    首先,好数会和模有什么关系?不用想了,反正我们已经打出了表,对一下表,我们就知道它和模似乎没有什么关系。所以,我们考虑暴力。
    暴力?不会爆炸吗?
    我可以很肯定地告诉你,不会爆炸。
    为什么?
    有一个很显然的结论:当a>ba>b时,amod  b<=a2a mod b<=frac{a}{2}
    可以感性理解,也可以理性证明:
    我们可以分类讨论:
    ab>a2ageq b>frac{a}{2}时,那么amod  b=aba2amod b =a-bleq frac{a}{2}
    ba2bleqfrac{a}{2}时,那么amod  b<ba2a mod b<bleqfrac{a}{2}
    得证。
    所以说,在每次操作中,某个数一定会缩小一半或以上。
    在没有修改的情况下,xx能取模lgxlg x次(当然xx大于模数)。

    我们有一种很巧妙的思想,这种思想其实也不少见了。
    有些操作看似暴力,实际上它的操作次数是有限的,所以你可以这么想:
    不管操作次数有多少次,反正我最多只做这么多遍,你管我啊?
    曾经我用过这个思想切掉了某一道题解说必须用块状链表,而不能用线段树的题目。我用了线段树AC,暴虐题解的感觉真爽……
    对于这题,我们可以用线段树维护。
    记录区间的好数个数还有最大值。
    我们在操作2时,可以先看看这个区间的最大值是多少,如果最大值大于模数,那么就进入这个区间。
    这样保证了进入每一个区间都不可能做无用功,所以保证了时间复杂度。
    对于每个数,最多被模lg106lg 10^6次,总共有nn个数,每次暴力模一个数最多花费lgnlg n的时间。所以说,不管怎样,你最多就暴力模了nlgnlg106nlg n lg 10^6次。
    实际上远远不到这么多,首先数据不会这么狠心地将所有数的价值榨干,其次,在进入一个区间后,处理一些去要处理的数往往是顺路的,也就是说,很难每次暴力下去模都花费lgnlg n的时间。
    可能你会问,修改怎么办?
    修改就修改喽,管它呢!
    你可以看做将原来的数删去,然后插入一个新的数。原来的数剩余被模的机会没了,新来的数还有一些被模的机会。然而,修改的次数也不多,是有范围的,级别也和nn差不多吧。就当成是多了这么多个数,实际上时间复杂度也不会被影响。

    然后呢,然后呢?当然是没有然后了。
    这题就被轻轻松松地解决了。

    代码

    using namespace std;
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #define N 100000
    #define MAX 1000000
    int n,m;
    bool is[MAX+1];
    int pri[MAX+1];
    int sum[N*4+1],mx[N*4+1];
    void build(int,int,int);
    int qsum(int,int,int,int,int);
    void find(int,int,int,int,int,int);
    void change_set(int,int,int,int,int);
    int main(){
    	memset(is,1,sizeof is);
    	//下面是求质数
    	for (int i=2;i<=MAX;++i){
    		if (is[i])
    			pri[++*pri]=i;
    		for (int j=1;i*pri[j]<=MAX && j<=*pri;++j){
    			is[i*pri[j]]=0;
    			if (i%pri[j]==0)
    				break;
    		}
    	}
    	//除了质数之外还有0,6,2^i
    	is[0]=1,is[6]=1;
    	for (int i=0;1<<i<=MAX;++i)
    		is[1<<i]=1;
    	scanf("%d%d",&n,&m);
    	build(1,1,n);
    	for (int i=1;i<=m;++i){
    		int op;
    		scanf("%d",&op);
    		if (op==1){
    			int l,r;
    			scanf("%d%d",&l,&r);
    			printf("%d
    ",qsum(1,1,n,l,r));
    		}
    		else if (op==2){
    			int l,r,mo;
    			scanf("%d%d%d",&l,&r,&mo);
    			find(1,1,n,l,r,mo);
    		}
    		else{
    			int x,y;
    			scanf("%d%d",&x,&y);
    			change_set(1,1,n,x,y);
    		}
    	}
    	return 0;
    }
    void build(int k,int l,int r){
    	if (l==r){
    		scanf("%d",&mx[k]);
    		sum[k]=is[mx[k]];
    		return;
    	}
    	int mid=l+r>>1;
    	build(k<<1,l,mid);
    	build(k<<1|1,mid+1,r);
    	sum[k]=sum[k<<1]+sum[k<<1|1];
    	mx[k]=max(mx[k<<1],mx[k<<1|1]);
    }
    int qsum(int k,int l,int r,int st,int en){
    	if (l==st && r==en)
    		return sum[k];
    	int mid=l+r>>1;
    	if (en<=mid)
    		return qsum(k<<1,l,mid,st,en);
    	if (mid<st)
    		return qsum(k<<1|1,mid+1,r,st,en);
    	return qsum(k<<1,l,mid,st,mid)+qsum(k<<1|1,mid+1,r,mid+1,en);
    }
    void find(int k,int l,int r,int st,int en,int mo){
    	if (mx[k]<mo)//如果没有可以被模的数,那就不要继续了
    		return;
    	if (l==r){
    		sum[k]=is[mx[k]%=mo];//暴力修改……
    		return;
    	}
    	int mid=l+r>>1;
    	if (en<=mid)
    		find(k<<1,l,mid,st,en,mo);
    	else if (mid<st)
    		find(k<<1|1,mid+1,r,st,en,mo);
    	else{
    		find(k<<1,l,mid,st,mid,mo);
    		find(k<<1|1,mid+1,r,mid+1,en,mo);
    	}
    	sum[k]=sum[k<<1]+sum[k<<1|1];
    	mx[k]=max(mx[k<<1],mx[k<<1|1]);
    }
    void change_set(int k,int l,int r,int x,int c){
    	if (l==r){
    		sum[k]=is[mx[k]=c];
    		return;
    	}
    	int mid=l+r>>1;
    	if (x<=mid)
    		change_set(k<<1,l,mid,x,c);
    	else
    		change_set(k<<1|1,mid+1,r,x,c);
    	sum[k]=sum[k<<1]+sum[k<<1|1];
    	mx[k]=max(mx[k<<1],mx[k<<1|1]);
    }
    

    总结

    首先,打表找规律是一样好东西。
    在遇到什么看似是奇葩数论题的时候,如果推不出来,就打表找规律。
    要知道有时候找规律比推式子简单多了。

    还有,以后见到一些不怎么好维护的东西,就要联想到类似的方法。
    如果操作是不可逆的东西,那就试着暴力搞。
    反正怎么搞顶多这么多次,那就暴力呗!

  • 相关阅读:
    原生ajax书写
    java 中的instanceof
    Leetcode-Python3
    快速读入挂
    HDU 6044 Limited Permutation(2017多校)【计数 快速读入挂 线性逆元】
    HDU 6015 Colorful Tree(2017多校)
    HDU 6034 【贪心】
    POJ 3415 Common Substrings 【长度不小于 K 的公共子串的个数】
    POJ Football Game 【NIMK博弈 && Bash 博弈】
    2018
  • 原文地址:https://www.cnblogs.com/jz-597/p/11145252.html
Copyright © 2011-2022 走看看