CF1174E Ehab and the Expected GCD Problem(DP,数论)
题目大意:对于一个序列,定义它的价值是它的所有前缀的 $\gcd$ 中互不相同的数的个数。给定整数 $n$,问在 $1$ 到 $n$ 的排列中,有多少个排列的价值达到最大值。答案对 $10^9+7$ 取模。
$2\le n\le 10^6$。
一道 Div. 2 的难度 2500 的题,真的不是吹的……
首先考虑排列的第一个数 。假如分解质因子后为 $\prod p_i^{c_i}$,那么此时排列价值的最大值为 $\sum c_i$。
为什么?因为如果 $\gcd$ 变了,那么一定变成原来 $\gcd$ 的约数。每次变化 $\sum c_i$ 至少 $-1$。所以最大值就是 $\sum c_i$。
那么排列的价值达到最大值,只有在第一个数的 $\sum c_i$ 达到最大值才可能,并且每次 $\gcd$ 变化只会令 $\sum c_i$ 减小 $1$。
首先发现,质因子 $p_i$ 中不会有 $\ge 5$ 的数。因为此时可以把 $p_i$ 变成 $2^2$,约数更多且仍然合法。
然后,设分解质因子后 $3$ 的次数为 $c$,那么 $0\le c\le 1$。因为当 $c\ge 2$ 时,可以把 $3^2$ 变成 $2^3$,约数更多且仍然合法。
所以第一个数可以被表示成 $2^x3^y$,其中 $y\in\{0,1\}$。
那么就能上DP了。(为什么每次都那么突然……)
设 $f[i][x][y]$ 表示目前填了前 $i$ 位,当前的 $\gcd$ 是 $2^x3^y$,的总合法序列数。
初始状态 $f[1][\lfloor\log_2n\rfloor][0]=1$。如果 $2^{\lfloor\log_2n\rfloor-1}\times 3\le n$,那么还有 $f[1][\lfloor\log_2n\rfloor-1][1]=1$。其它的状态无用,只有这两个状态的 $x+y$ 达到了最大值。
答案为 $f[n][0][0]$。因为排列包含 $1$,所以 $\gcd$ 一定会变为 $1$。
如何转移?(以下设 $cnt(x)=\lfloor\frac{n}{x}\rfloor$,即 $x$ 的倍数的个数)
- $\gcd$ 不变。那么 $f[i][x][y]+=f[i-1][x][y](cnt(2^x3^y)-(i-1))$。因为新选择的数可以是且一定是 $2^x3^y$ 的倍数。然而前 $i-1$ 个位置都是 $2^x3^y$ 的倍数,所以要减掉。
- $\gcd/2$,也就是 $x--$(此时要求 $x<\lfloor\log_2n\rfloor$)。那么 $f[i][x][y]+=f[i-1][x+1][y](cnt(2^x3^y)-cnt(2^{x+1}3^y))$。因为新选择的数一定是 $2^x3^y$ 的倍数,但一定不是 $2^{x+1}3^y$ 的倍数(否则 $\gcd$ 不变)。前 $i-1$ 个位置都是 $2^{x+1}3^y$ 的倍数,所以不用减掉。
- $\gcd/3$,也就是 $y--$(此时要求 $y=0$)。那么 $f[i][x][y]+=f[i-1][x][y+1](cnt(2^x3^y)-cnt(2^x3^{y+1}))$。
时间复杂度 $O(n\log n)$。
#include<bits/stdc++.h> using namespace std; typedef long long ll; typedef pair<int,int> PII; const int maxn=1000100,mod=1000000007; #define MP make_pair #define PB push_back #define lson o<<1,l,mid #define rson o<<1|1,mid+1,r #define FOR(i,a,b) for(int i=(a);i<=(b);i++) #define ROF(i,a,b) for(int i=(a);i>=(b);i--) #define MEM(x,v) memset(x,v,sizeof(x)) inline ll read(){ char ch=getchar();ll x=0,f=0; while(ch<'0' || ch>'9') f|=ch=='-',ch=getchar(); while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar(); return f?-x:x; } int n,lt,f[maxn][21][2]; inline int cnt(int x){return n/x;} int main(){ n=read(); lt=log2(n); f[1][lt][0]=1; if((1<<(lt-1))*3<=n) f[1][lt-1][1]=1; FOR(i,2,n) FOR(j,0,lt){ f[i][j][0]=(1ll*f[i-1][j][0]*(cnt(1<<j)-(i-1))+1ll*f[i-1][j+1][0]*(cnt(1<<j)-cnt(1<<(j+1)))+1ll*f[i-1][j][1]*(cnt(1<<j)-cnt((1<<j)*3)))%mod; f[i][j][1]=(1ll*f[i-1][j][1]*(cnt((1<<j)*3)-(i-1))+1ll*f[i-1][j+1][1]*(cnt((1<<j)*3)-cnt((1<<(j+1))*3)))%mod; } printf("%d\n",f[n][0][0]); }