zoukankan      html  css  js  c++  java
  • P5655 基础数论函数练习题

    P5655 基础数论函数练习题

    推到后面被卡常了就完全不会。。。神qwaszx!真不知道这些神仙是如何想到做法的

    这题的难点在于很多地方不能随便取模,而且值域特别大

    写篇题解总结一下。

    众所周知(其实也很好证明,我就不证了)

    [operatorname{lcm}(a,b)=adfrac{b}{gcd(a,b)} ]

    考虑如何把那一堆 (operatorname{lcm}) 搞成可以乘的形式,这样就可以边乘边取模了

    [b_i=dfrac{a_i}{gcd(a_i,prod_{j>i}b_j)}\ operatorname{lcm}(a_l,a_{l+1},dots,a_r)=prod_{i=l}^{r} b_i\ ]

    (上面就是连续使用最上头那个性质的结果)

    然后我卡住了,这个式子还是没什么用,那个 (prod_{j>i}b_j) 大的离谱

    瞟了题解,可以每次往右扩展一个数 (a_k)

    这时候 (b_k=a_k,b_i o b_i') (显然 (b'_ile b_i)),令 (c_i=dfrac{b_i}{b'_i}) ,那么 (prod c_ile a_k) ,因为是 (operatorname{lcm}) 。这样就有了一个可以 (pi) 的东西(别问我为啥写 (pi) ,因为我就是这么读的)

    首先

    [b'_i=dfrac{b_i}{gcd(b_i,prod_{j>i}b'_j)} ]

    就是倒着求 (b') ,前面因为多了 (a_k) ,已经消掉的因子就不用统计了

    那个 (prod b'_i) 仍然太大,但是我们可以通过求出 (c_i) 来修改每一个 (b_i)

    [c_i=gcd(b_i,dfrac{a_k}{prod_{j>i}c_j}) ]

    (c_i) 这个式子的来源还是约掉已经约掉的,留下有用的。

    因为 (prod c_i) 不会超long long,所以直接 (pi) 就行了

    这样就可以 $O(Tn^2log V) $ 解决这题了。

    可惜出题人并不认为你解决了,卡掉了这个复杂度,你 TLE 90 了。

    可是 (color{black}{ exttt{c}}color{red}{ exttt{yn2006}}) 也是这个复杂度没卡常就过去了。这就是神仙与蒟蒻的差别。

    我尝试搞些科技来卡常

    瓶颈在于有 (n^2)(gcd) ,这让我想到了一个叫做“快速gcd”的东西,大致思路是辗转相减,二进制优化

    LL gcd(LL x,LL y){
    	if(!x||!y)return x|y;
    	if(!(x&1)&&!(y&1))return gcd(x>>1,y>>1)<<1;
    	if(!(x&1))return gcd(x>>1,y);
    	if(!(y&1))return gcd(x,y>>1);
    	return gcd(abs(x-y),min(x,y));
    }
    

    然后发现多 TLE 了一个点 什么鬼哦,龟速gcd

    自闭了去看 qwaszx 的题解。

    他就是利用了一个性质来减少 (gcd) 的次数 然而这个性质在我以前验题的时候自己想到过

    看看我们是如何求 (c_i)

    [c_i=gcd(b_i,dfrac{a_k}{prod_{j>i}c_j}) ]

    所以不是 (1)(c_i) 至多 (log V) 个,因为每一个不是 (1)(c_i) 都会导致 (a_k) 至少除以 (2) ,所以我们只需要判断 (c_i) 是否是 (1) ,于是求 (gcd) 的次数变成了 (nlog V)

    如何快速判断 (c_i) 的值呢?

    首先有俩性质:

    [gcd(xy,a) ot=gcd(y,a)Leftrightarrow gcd(xy,a) otmid y ]

    这个逻辑表达式的两边只要有一个满足,就说明存在质因数 (p) 满足 (min(p_x+p_y,p_a)>min(p_y,p_a)) (其中 (p_i) 表示 (p)(i) 中的最高次数) 那么另一边必然成立

    [ymod gcd(xy,a) ot=0Leftrightarrow (ymod a)mod gcd(xy,a) ot =0 ]

    这个非常显然, (a)(gcd(xy,a)) 的倍数,而 (xmod aequiv (x-ka)mod a)

    (s_i=prod_{j=i}^{k}b'_j)

    套用上述两个性质可得

    [gcd(s_i,a_k) ot=gcd(s_{i+1},a_k)Leftrightarrow gcd(s_i,a_k) otmid s_{i+1}Leftrightarrow (s_{i+1}mod a_k)mod gcd(s_i,a_k) ot= 0 ]

    所以每次扩展时算出 (s_imod a_k) ,记录 (gcd(s_i,a_k)) ,取模判一下是否重算 (gcd) 即可

    但是还有个问题,就是 (s_imod a_k) 怎么处理,如果龟速乘会多个 (log V) ,复杂度是 (O(T(nlog^3 V+n^2)))

    (n) 只有 (300) ,但是 (log V) 只有 (60) ,比原来的复杂度还劣。。。

    又是一个科技:光速乘,它是 (O(1)) 计算 long long 级别的取模乘法的

    LL mul(LL a,LL b,LL P){
    	LL res=a*b-(LL)((long double)a/P*b+0.5)*P;
    	return res<0?res+P:res;
    }
    

    __int128 也行,但是NOIP用不了啊。。。

    我感觉上面那个光速乘挺牛逼的,跑了一下 ((2^{60}-1)(2^{60}+1)mod 2^{60}) 居然跑对了,精损并没有想象中那么严重

    AC代码:

    #define N 305
    #define mod 1000000007
    int n,q,ans[N][N];
    LL a[N],b[N],s[N];
    LL mul(LL a,LL b,LL P){
    	LL res=a*b-(LL)((long double)a/P*b+0.5)*P;
    	return res<0?res+P:res;
    }
    void init(){
    	for(int i=1;i<=n;++i){
    		b[i]=a[i],s[i]=1;
    		for(int j=i-1;j>=1;--j)s[j]=mul(s[j+1],b[j],a[i]);
    		LL t=__gcd(s[1],a[i]);
    		for(int j=1;j<i;++j){
    			if(s[j+1]%t){
    				LL gcd=__gcd(t,s[j+1]);
    				b[j]/=t/gcd,t=gcd;
    			}
    		}
    		ans[i][i]=a[i]%mod;
    		for(int j=i-1;j>=1;--j)ans[j][i]=1ll*ans[j+1][i]*(b[j]%mod)%mod;
    	}
    }
    void Main(){
    	scanf("%d%d",&n,&q);
    	for(int i=1;i<=n;++i)scanf("%lld",&a[i]);
    	init();
    	while(q--){int l,r;scanf("%d%d",&l,&r);printf("%d
    ",ans[l][r]);}
    }
    signed main(){for(int T=read();T;--T)Main();}
    
    

    TLE的

    #define N 305
    #define mod 1000000007
    int n,q,ans[N][N];
    LL a[N],b[N],c[N];
    LL gcd(LL x,LL y){return !y?x:gcd(y,x%y);}
    void init(){
    	for(int i=1;i<=n;++i){
    		b[i]=a[i],c[i]=1;
    		for(int j=i-1;j>=1;--j){
    			c[j]=gcd(b[j],a[i]/c[j+1]);
    			b[j]/=c[j];
    			c[j]*=c[j+1];
    		}
    		ans[i][i]=a[i]%mod;
    		for(int j=i-1;j>=1;--j)ans[j][i]=1ll*ans[j+1][i]*(b[j]%mod)%mod;
    	}
    }
    void Main(){
    	scanf("%lld%lld",&n,&q);
    	for(int i=1;i<=n;++i)scanf("%lld",&a[i]);
    	init();
    	while(q--){int l,r;scanf("%lld%lld",&l,&r);printf("%lld
    ",ans[l][r]);}
    }
    signed main(){for(int T=read();T;--T)Main();}
    
    路漫漫其修远兮,吾将上下而求索
  • 相关阅读:
    WPF中样式中事件处理
    模板方法
    WPF 中ComboBox绑定DataSet
    装饰模式
    【转帖】c#项目的打包部署教材图文
    WPF中用比例设置长度或宽度
    在WFP中完美使用winform的控件
    一个WPF软件作品新鲜出炉,欢迎拍砖
    建造者模式(生成器模式)
    Windows Presentation Foundation 中的 Pack URI
  • 原文地址:https://www.cnblogs.com/zzctommy/p/13878016.html
Copyright © 2011-2022 走看看