毒瘤 dp 题。
首先显然可以莽一波结论。
可以发现,当排列的第一个数 a1=∏pici,pi is prime 时,题目中所求排列的权值最大值为 ∑ci。
这是因为在第二个数及以后,前缀 gcd 只会减小,不会增加,而且最好的情况是每次减小一个质因子。可以证明只要有一个确定的第一个数,该最好情况一定可以实现。
那么我们可以考虑找出使 ∑ci 最大的一个数 a1。
再来猜一波:这个 a1 的可能性不会太多,它的质因子得小。因为如果有比较大的质因子的话,它的指数 ci 就会被压榨,会比质因子更小的情况劣。
那么质因子得有多小呢?我们先猜:这个 a1=2x。
这种情况确实很优,但不久后我们还会发现,2x⋅3y 似乎也很可以。
但是到 2x⋅32 之后就爆炸了,因为有一个比它小但质因数分解后的指数和比它大的数 2x+3。
因此,质因子 3 的次数不能大于 1。
那么能不能有比 3 大的质因子呢?显然不能。因为 5>22,用了 5 及以上的不如用一堆 2 来代替。
那么我们可以得出结论:a1 只能等于 2 的若干次方,或者 2 的若干次方乘上 3。
那么就可以愉快设 dp 状态了:令 fi,j,k 表示到了该排列的第 i 个数,当前前缀 gcd=2j⋅3k 时,有多少种情况。
转移可以分三类讨论:
- 若加入一个数后 gcd 不变,那么 fi+1,j,k 需要加上 fi,j,k⋅(⌊2j⋅3kn⌋−i)。因为 gcd 不变,那么加入的数一定是当前 gcd 的倍数,需要注意前面的所有数一定都是当前 gcd 的倍数,不可以选已经选过的数,于是情况数需要 −i。
- 若加入一个数后 gcd 变少了 2 倍,那么 fi+1,j−1,k 需要加上 fi,j,k⋅(⌊2j−1⋅3kn⌋−⌊2j⋅3kn⌋)。由于 gcd 变少两倍,那么新加入的数一定得是 2gcd 的倍数,但它不能是 gcd 本身的倍数,因为这样的话 gcd 就不会改变了。而在 2gcd 的倍数和 gcd 本身的倍数中,都考虑了一遍已经加入过的数,抵消掉了,因此不会加入重复的数。
- 若加入一个数后 gcd 变少 3 被,那么 fi+1,j,k−1 需要加上 fi,j,k⋅(⌊2j⋅3k−1n⌋−⌊2j⋅3kn⌋)。由于 gcd 变少两倍,那么新加入的数一定得是 3gcd 的倍数,但它不能是 gcd 本身的倍数。类似地,在 3gcd 的倍数和 gcd 本身的倍数中,也抵消掉了重复的数。因此这么干是对的。
代码实现上因为 3 只有可能是 0 次方或 1 次方,可以用三目运算符判断更加方便。
PS:这题十分卡常,还卡 long long
数组(如果 dp
数组用 long long
的话会炸)的空间。我是用火车头 + C++17 才过的(
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| #include <bits/stdc++.h> #define rep(i,a,b) for(int i=(a);i<=(b);++i) #define per(i,a,b) for(int i=(a);i>=(b);--i) using namespace std; const int mod=1e9+7; int n; int dp[1000005][21][3]; int qaq(int x,int y) { return (1<<x)*(y?3:1)%mod; } int main() { scanf("%d",&n); int hav3=log2(n/3)+1; int nhav3=log2(n); dp[1][nhav3][0]=1; if(hav3==nhav3) dp[1][nhav3-1][1]=1; rep(i,1,n-1) { rep(j,0,nhav3) { rep(k,0,1) { dp[i+1][j][k]=1ll*(1ll*dp[i+1][j][k]+dp[i][j][k]*(1ll*n/qaq(j,k)%mod-i+mod)%mod+mod)%mod; if(j>0) dp[i+1][j-1][k]=1ll*(1ll*dp[i+1][j-1][k]+dp[i][j][k]*(1ll*n/qaq(j-1,k)%mod-n/qaq(j,k)%mod)%mod+mod)%mod; if(k>0) dp[i+1][j][k-1]=1ll*(1ll*dp[i+1][j][k-1]+dp[i][j][k]*(1ll*n/qaq(j,k-1)%mod-n/qaq(j,k)%mod)%mod+mod)%mod; } } } printf("%d\n",dp[n][0][0]); return 0; }
|