怎样跑得更快
大力水手问禅师:“大师,我觉得我光有力气是不够的。比如我吃菠菜可以让力气更大,但是却没有提升跑步的速度。请问怎样才能跑得更快?我试过吃白菜,没有效果。”
禅师浅笑,答:“方法很简单,不过若想我教你,你先看看这道UOJ Round的C题。”
令 \(p = 998244353\) (\(7 \times 17 \times 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\) 满足:
\[
\begin{equation}
\sum_{j = 1}^{n} \gcd(i, j)^c \cdot \text{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\) 的最大公约数,\(\text{lcm}(i, j)\) 表示 \(i\) 和 \(j\) 的最小公倍数。
有 \(q\) 个询问,每次给出 \(b_1, \dots, b_n\),请你解出 \(x_1, \dots, x_n\) 的值。
对于所有数据,\(nq \leq 3 \times 10^5\),\(0 \leq c, d \leq 10^9\)。
题解
不错,UOJ Round的题质量很高,还有高质量题解。
\[
\sum_{j=1}^n \gcd(i,j)^c \text{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的题解。
其实这种题都可做:
\[ \begin{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 \text{且} d \neq 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_{d\mid i} f_r(d) z_d=b_i
\]
用同样的手段得到
\[
f_r(i) z_i = \left\{\begin{align*}
\sum_{d\mid i} b_i \mu (\frac{d}{i})\\
b_i - \sum_{d\mid i \and d \neq i} f_r(i)z_i
\end{align*}\right.
\]
任选其一解出\(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\rfloor} x_{dj} =z_d\\
x_d=z_d-\sum_{j=2}^{\lfloor \frac nd\rfloor} 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] \neq 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; }