zoukankan      html  css  js  c++  java
  • UOJ62 怎样跑得更快

    怎样跑得更快

    大力水手问禅师:“大师,我觉得我光有力气是不够的。比如我吃菠菜可以让力气更大,但是却没有提升跑步的速度。请问怎样才能跑得更快?我试过吃白菜,没有效果。”

    禅师浅笑,答:“方法很简单,不过若想我教你,你先看看这道UOJ Round的C题。”

    (p = 998244353)(7 imes 17 imes 2^{23} + 1),一个质数)。

    给你整数 (n, c, d)。现在有整数 (x_1, dots, x_n)(b_1, dots, b_n) 满足 (0 leq x_1, dots, x_n, b_1, dots, b_n < p),且对于 (1 leq i leq n) 满足:

    [egin{equation} sum_{j = 1}^{n} gcd(i, j)^c cdot ext{lcm}(i, j)^d cdot x_j equiv b_i pmod{p} end{equation} ]

    其中 (v equiv u pmod{p}) 表示 (v)(u) 除以 (p) 的余数相等。(gcd(i, j)) 表示 (i)(j) 的最大公约数,( ext{lcm}(i, j)) 表示 (i)(j) 的最小公倍数。

    (q) 个询问,每次给出 (b_1, dots, b_n),请你解出 (x_1, dots, x_n) 的值。

    对于所有数据,(nq leq 3 imes 10^5)(0 leq c, d leq 10^9)

    题解

    不错,UOJ Round的题质量很高,还有高质量题解。

    [sum_{j=1}^n gcd(i,j)^c ext{lcm}(i,j)^d x_j = b_i\ sum_{j=1}^n gcd(i,j)^{c-d} i^d j^d x_j=b^i ]

    这里为了方便,令(c‘=c-d,x’_j=j^d x_j,b‘_i=frac {b^i}{i^d})

    [sum_{j=1}^n gcd(i,j)^c x_j=b^i ]

    这里出现了问题。我们没法像以前那样化简了,因为(gcd)后面跟了未知数,而未知数的下标我们不能带进去算。

    但是我们的确需要玩约数和倍数的trick来化简式子。这里贴下vfleaking的题解。

    其实这种题都可做:

    [egin{equation} sum_{j=1}^n f(gcd(i,j)) cdot g(i) cdot h(j) cdot x_j = b_i end{equation} ]

    可能很多人的注意力都在“这玩意儿怎么解啊”,其实只要换个姿势问问自己 “要是有人告诉了我 (x),我应该怎么验证它是对的呢?” 这题就可做了。

    其实关键的坑人的地方在于 (f(gcd(i,j)))。假设我有一个函数 (f_r(n)),满足 (f(n) = sum_{d mid n}{f_r(d)}),其中 (d mid n) 表示 (d)(n) 的约数。这个式子的意思是,(f(n)) 等于所有 (n) 的约数带进 (f_r) 后的和。知道 (f)(f_r) 是很好搞的,因为 (f_r(n) = f(n) - sum_{d mid n ext{且} d eq n}{f_r(d)}),所以就能递推了。妈呀,怎么枚举约数啊?其实只要这样搞就行了:

    for (int i = 1; i <= n; i++)
        f_r[i] = f[i];
    for (int i = 1; i <= n; i++)
        for (int j = i + i; j <= n; j += i)
            f_r[j] -= f_r[i];
    

    看起来是 (O(n^2)) 的?好吧如果你不知道这是 (O(n log n)) 的话就是个悲伤的故事。由于
    (sum_{i = 1}^{n}{frac{1}{i}} = O(log n)),所以 (sum_{i = 1}^{n}{frac{n}{i}} = O(n log n))

    为什么要这样?因为我们知道如果 (d mid gcd(i, j)) 那么肯定有 (d mid i)(d mid j),反之亦然。这样就把讨厌的 (gcd) 给去掉了。

    也可以通过每个初学者都会看到但可能永远用不着的式子(f=g*I Rightarrow g=f*mu)解决求(f_r)的问题,时间复杂度(O(n log n))

    [sum_{j=1}^n f(gcd(i,j)) x_j =b_i\ sum_{j=1}^n sum_{d|i and d|j} f_r(d) x_j=b_i\ sum_{d|i} f_r(d) sum_{j=1}^n [d mid j] x_j=b_i ]

    后面那个求和式是在枚举(d)的倍数,只与(d)有关,所以先记为(z_d)

    [sum_{dmid i} f_r(d) z_d=b_i ]

    用同样的手段得到

    [f_r(i) z_i = left{egin{align*} sum_{dmid i} b_i mu (frac{d}{i})\ b_i - sum_{dmid i and d eq i} f_r(i)z_i end{align*} ight. ]

    任选其一解出(f_r(i)z_i),即可求得(z_i)。然后回代(z)的原式。

    [sum_{j=1}^n [d mid j] x_j = z_d\ sum_{j=1}^{lfloor frac nd floor} x_{dj} =z_d\ x_d=z_d-sum_{j=2}^{lfloor frac nd floor} x_{dj} ]

    所以,继续vfleaking的题解。

    for (int i = 1; i <= n; i++)
     hx[i] = z[i];
    for (int i = n; i >= 1; i--)
     for (int j = i + i; j <= n; j += i)
         hx[i] -= hx[j];
    

    嗯,现在我们知道了 (h(j) x_j),那么 (x_j) 就好求了。

    由于中间过程涉及了除法,所以就会带来无解和多解的情况。对于本题 (g(i))(h(j)) 肯定都不是 (p) 的倍数,所以都有逆元。而 (f_r(d)) 可能没有逆元。这种情况假如 (f_z[d] eq 0) 那么显然无解,如果 (f_z[d] = 0) 就有多组解,我们把 (z_d) 随便附个值比如让 (z_d = 0) 就好了。

    罗嗦了半天其实跟算法五本质是一样的。这题其实就是把 (b) 除以 (g(i)) 然后莫比乌斯反演,然后除以 (f) 的莫比乌斯反演,再进行莫比乌斯反演,再除以 (h(j)),三个莫比乌斯反演掷地有声。

    时间复杂度(O(qn log n))

    #include<bits/stdc++.h>
    #define co const
    #define il inline
    template<class T> T read(){
    	T x=0,w=1;char c=getchar();
    	for(;!isdigit(c);c=getchar())if(c=='-') w=-w;
    	for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    	return x*w;
    }
    template<class T> il T read(T&x){
    	return x=read<T>();
    }
    using namespace std;
    typedef long long LL;
    
    co int mod=998244353;
    il int add(int a,int b){
    	return (a+=b)>=mod?a-mod:a;
    }
    il int mul(int a,int b){
    	return (LL)a*b%mod;
    }
    il int fpow(int a,int b){
    	int ans=1;
    	for(;b;b>>=1,a=mul(a,a))
    		if(b&1) ans=mul(ans,a);
    	return ans;
    }
    
    co int N=100000+10;
    int main(){
    	int n=read<int>(),c=read<int>()%(mod-1),d=read<int>()%(mod-1);
    	c=(c+mod-1-d)%(mod-1);
    	
    	static int f[N];
    	for(int i=1;i<=n;++i) f[i]=fpow(i,c);
    	for(int i=1;i<=n;++i) // fr
    		for(int j=i+i;j<=n;j+=i)
    			f[j]=add(f[j],mod-f[i]);
    	
    	for(int q=read<int>();q--;){
    		static int b[N];
    		for(int i=1;i<=n;++i) b[i]=mul(read<int>(),fpow(i,mod-1-d));
    		
    		for(int i=1;i<=n;++i) // fr * z
    			for(int j=i+i;j<=n;j+=i)
    				b[j]=add(b[j],mod-b[i]);
    		
    		bool valid=1;
    		for(int i=1;i<=n;++i){ // z
    			if(f[i]!=0) b[i]=mul(b[i],fpow(f[i],mod-2));
    			else if(b[i]!=0) {valid=0;break;}
    		}
    		if(!valid) {puts("-1");continue;}
    		
    		for(int i=n;i>=1;--i) // x
    			for(int j=i+i;j<=n;j+=i)
    				b[i]=add(b[i],mod-b[j]);
    		
    		for(int i=1;i<=n;++i) printf("%d ",mul(b[i],fpow(i,mod-1-d)));
    		puts("");
    	}
    	return 0;
    }
    
  • 相关阅读:
    Spring Boot 的各种start
    Lombok介绍、使用方法和总结
    JS字符串与二进制的相互转化
    java字符串与二进制的相互转化
    DevExpress WPF v18.2新版亮点(六)
    用MyEclipse开发REST Web Service
    .NET界面控件DevExpress发布v18.2.4|附下载
    DevExpress WPF v18.2新版亮点(五)
    「版本升级」MyEclipse CI 2018.12.0正式发布
    MyEclipse使用教程:在Web项目中使用Web片段
  • 原文地址:https://www.cnblogs.com/autoint/p/11369231.html
Copyright © 2011-2022 走看看