反演套 DP 的好题(不用反演貌似也能作app
Description
Vivek initially has an empty array $a$ and some integer constant $m$.dom
He performs the following algorithm:优化
- Select a random integer $x$ uniformly in range from $1$ to $m$ and append it to the end of $a$.
- Compute the greatest common divisor of integers in $a$.
- In case it equals to $1$, break
- Otherwise, return to step $1$.
Find the expected length of $a$. It can be shown that it can be represented as $\frac PQ$ where $P$ and $Q$ are coprime integers and $Q\neq 0\pmod{10^9+7}$. Print the value of $P\cdot Q^{-1}\pmod{10^9+7}$.ui
Input
The first and only line contains a single integer $m$($1\le m\le 100000$).spa
Output
Print a single integer — the expected length of the array $a$ written as $P\cdot Q^{-1}\pmod{10^9+7}$.code
Examples
inputorm
1outputip
1inputinput
2outputstring
2input
4output
333333338Note
In the first example, since Vivek can choose only integers from $1$ to $1$, he will have $a=[1]$ after the first append operation, and after that quit the algorithm. Hence the length of $a$ is always $1$, so its expected value is $1$ as well.
In the second example, Vivek each time will append either $1$ or $2$, so after finishing the algorithm he will end up having some number of $2$'s (possibly zero), and a single $1$ in the end. The expected length of the list is $1⋅\frac 12+2⋅\frac 1{2^2}+3⋅\frac 1{2^3}+\dots =2$.
题意
每一步在 $1\sim m$ 中任选一个整数,问指望多少步后选出的数的最大公约数是 $1$。答案对 $1\ 000\ 000\ 007$ 取模。
题解
由于每一步不会让已经选了的元素的 $\gcd$ 和变大,所以认为是一个除自环外的有向无环图。对于自环咱们很好处理,因此把它当作是一道指望 DP。
令 $f[i]$ 表示当前的 $\gcd$ 和为 $i$,到 $\gcd$ 和为 $1$ 的状态的指望步数。所以把状态转移方程写出来 $$ f[i]=1+\frac{\sum_{j=1}^m f[\gcd(i,j)]}m $$ 这样的转移是 $O(m^2)$ 的。可是咱们发现,对于不少 $j$,$\gcd(i,j)$ 都是相等的,所以咱们把这样的数整合到一块儿。
令 $F(n)$ 表示 $1\sim m$ 中有多少个数 $i$ 知足 $\gcd(x,i)=n$,其中视 $x$ 为常数。
则计算 $f[i]$ 就转化为了 $$ f[i]=1+\frac{\sum_{d|i}f[d]\times F(d)}n,x=i $$ 这样差很少就把枚举优化到了 $\log n$ 的 $d|i$。
考虑怎么计算 $F(n)$ $$ F(n)=\sum_{i=1}^m[\gcd(x,i)=n] $$ 令 $G(n)=\sum_{n|d}F(d)$,则 $$ \begin{aligned} G(n)&=\sum_{n|d}F(d)\ &=\sum_{n|d}\sum_{i=1}^m[\gcd(x,i)=d]\ &=\sum_{i=1}^m[n|\gcd(x,i)]\ ?&=\sum_{i=1}^{\left\lfloor\frac mn\right\rfloor}\left[1|\gcd\left(\frac xn,i\right)\right] \end{aligned} $$ 实际上这样是有问题的,由于(在后面)没法保证 $n|x$,此时 $G(n)$ 就必定为 $0$ 了。
咱们再多化一步: $$ \begin{aligned} G(n)&=\sum_{i=1}^{\left\lfloor\frac mn\right\rfloor}\left[1|\gcd\left(\frac xn,i\right)\right][n|x]\ &=\left\lfloor\frac mn\right\rfloor\cdot[n|x] \end{aligned} $$ 根据 $G(n)=\sum_{n|d}F(d)$,咱们反演到 $F$,得 $$ \begin{aligned} F(n)&=\sum_{n|d}\mu\left(\frac dn\right)G(d)\ &=\sum_{i=1}^{\left\lfloor\frac mn\right\rfloor}\mu(i)G(ni)\ &=\sum_{i=1}^{\left\lfloor\frac mn\right\rfloor}\mu(i)\left\lfloor\frac{m}{ni}\right\rfloor[(ni)|x] \end{aligned} $$ 咱们发现后面的布尔表达式能够看成条件。本来的条件原本就是 $\to +\infty$ 的,只不过超过了 $\left\lfloor\frac mn\right\rfloor$ 没有意义。所以直接把条件换成 $[(ni)|x]$ 便可。又由于 $n|x$ 在上面的枚举过程当中是成立的,同时能够转化为 $\left[i|\frac xn\right]$。 $$ F(n)=\sum_{i|\frac xn}\mu(i)\left\lfloor\frac{m}{ni}\right\rfloor $$ 这样的一次枚举是 $O\left(d\left(\frac xn\right)\right)$ 的,因为 $1\sim m$ 的约数个数和均摊是 $O(\log m)$ 的,其中最多的有 $128$ 个约数,可是这样的数确定不是不少,而且其中不少被枚举到的数都是质因数,迭代一下并不会形成很大的复杂度。
而后咱们须要再把状态转移方程稍微转化一下,把 $f[i]$ 移到左边 $$ \begin{aligned} f[i]&=1+\frac{\sum_{d|i,d<i}f[d]\times F(d)+f[i]\times F(i)}n,x=i\ \frac{n-F(i)}{n}\cdot f[i]&=1+\frac{\sum_{d|i,d<i}f[d]\times F(d)}n,x=i\ f[i]&=\frac{n+\sum_{d|i,d<i}f[d]\times F(d)}{n-F(i)},x=i \end{aligned} $$ 就获得了真正的转移方程。
时间复杂度 $O(m\log^2 m)$。
Code:
#include<cstdio> #include<cstring> #include<vector> #define ll long long #define p 1000000007 using std::vector; vector<int> v[100100];//约数用 vector 存一下,每次 √m 枚举不是很稳 ll qpow(ll x,ll y) { ll ans=1; while(y) { if(y&1) ans=ans*x%p; x=x*x%p; y>>=1; } return ans; } bool is[100100]; int pri[100100],mu[100100],cnt=0; ll f[100100]; int n; int calc(int x,int y)//1~n 中 gcd(x,i)=y 的数的个数 { int g=x/y,ans=0; for(int i=0;i<v[g].size();++i) ans+=mu[v[g][i]]*(n/v[g][i]/y); return ans; } int main() { scanf("%d",&n); f[1]=1; mu[1]=1; for(int i=2;i<=n;++i) { if(!is[i]) { pri[++cnt]=i; mu[i]=-1; } for(int j=1;j<=cnt&&i*pri[j]<=n;++j) { is[i*pri[j]]=1; if(i%pri[j]) mu[i*pri[j]]=-mu[i]; else { mu[i*pri[j]]=0; break; } } } for(int i=1;i<=n;++i) for(int j=i;j<=n;j+=i) v[j].push_back(i); ll ans=1,inv=qpow(n,p-2); for(int i=2;i<=n;++i) { for(int j=0;j<v[i].size()-1;++j) f[i]=(f[i]+calc(i,v[i][j])*f[v[i][j]]%p)%p; f[i]=(f[i]*inv+1)%p; ll g=n-calc(i,i); f[i]=f[i]*n%p*qpow(g,p-2)%p; ans=(ans+f[i])%p; } printf("%lld\n",ans*qpow(n,p-2)%p); return 0; }