CF1174E (Ehab and the Expected GCD Problem) 題解——數論與dp的完美結合
Description
Solution
Lemma 1
g i + 1 = t g i ( t ∈ N ∗ ) g_{i+1}=t\ g_i(t \in N^*) gi+1=tgi(t∈N∗),且 t ≤ 3 t \le 3 t≤3。
Certification 1
根據 gcd \gcd gcd的定義,不難發現 g i + 1 g_{i+1} gi+1一定是 g i g_i gi的約數。
例如,一個合法的
g
g
g序列是
{
12
,
6
,
6
,
6
,
6
,
3
,
2
,
2
,
1
,
1
,
1
,
1
,
1
,
1
,
1
,
1
}
\{ 12,6,6,6,6,3,2,2,1,1,1,1,1,1,1,1\}
此時 f ( p ) f(p) f(p)就是 g ′ g' g′的長度。當 f ( p ) f(p) f(p)最大時,我們肯定要貪心地讓 g i + 1 ′ g'_{i+1} gi+1′比 g i ′ g'_i gi′小的倍數越少越好,否則 g ′ g' g′的長度必然被削減。
令這個小的倍數為
t
′
t'
t′,不難發現
t
′
>
2
t'>2
擴充套件得,當
4
≤
t
′
4 \le t'
4≤t′時
g
′
g'
g′一定不是最優。所以Lemma 1中
t
≤
3
t \le 3
證畢。
Lemma 2
g i + 1 = t g i ( t ∈ N ∗ ) g_{i+1}=t\ g_i(t \in N^*) gi+1=tgi(t∈N∗),且 t ≤ 3 t \le 3 t≤3;更進一步的, t = 3 t=3 t=3的次數最多隻有1次。
Certification 1
考慮到 2 3 ≤ 3 2 2^3 \le 3^2 23≤32,所以連續兩次 t = 3 t=3 t=3肯定差於三次 t = 2 t=2 t=2,所以 t = 3 t=3 t=3的次數最多隻有 1 1 1次。
考慮 d p dp dp。
狀態設計: d p i , j , k dp_{i,j,k} dpi,j,k: 看到第 i i i個數,目前 gcd \gcd gcd為 2 j 3 k 2^j 3^k 2j3k。
狀態轉移:
①
gcd
\gcd
gcd不變。
d
p
i
,
j
,
k
=
(
⌊
n
p
⌋
−
i
+
1
)
d
p
i
−
1
,
j
,
k
dp_{i,j,k}=(\lfloor \frac n p \rfloor-i+1)dp_{i-1,j,k}
dpi,j,k=(⌊pn⌋−i+1)dpi−1,j,k
②
gcd
\gcd
gcd除以
2
2
2(
j
+
1
≤
⌊
log
n
⌋
j+1 \le \lfloor \log n \rfloor
j+1≤⌊logn⌋):
d
p
i
,
j
,
k
=
(
⌊
n
2
j
3
k
⌋
−
⌊
n
2
j
+
1
3
k
⌋
)
d
p
i
,
j
+
1
,
k
dp_{i,j,k}=(\lfloor \frac n {2^j 3^k} \rfloor-\lfloor \frac n {2^{j+1}3^k} \rfloor)dp_{i,j+1,k}
dpi,j,k=(⌊2j3kn⌋−⌊2j+13kn⌋)dpi,j+1,k
③
gcd
\gcd
gcd除以
3
3
3(
k
=
0
k=0
k=0):
d
p
i
,
j
,
k
=
(
⌊
n
2
j
3
k
⌋
−
⌊
n
2
j
3
k
+
1
⌋
)
d
p
i
,
j
,
k
+
1
dp_{i,j,k}=(\lfloor \frac n {2^j 3^k}\rfloor-\lfloor \frac n {2^j 3^{k+1}} \rfloor)dp_{i,j,k+1}
dpi,j,k=(⌊2j3kn⌋−⌊2j3k+1n⌋)dpi,j,k+1
為什麼③的合法轉移要求 k = 0 k=0 k=0呢?因為,對於一次③的轉移相當於使 t t t成為了一次 3 3 3。根據之前的引理, t = 3 t=3 t=3的次數只能有 1 1 1次。
邊界:
①
d
p
1
,
⌊
log
n
⌋
,
0
=
1
dp_{1,\lfloor \log n \rfloor,\ 0}=1
dp1,⌊logn⌋,0=1
②
d
p
1
,
⌊
log
n
⌋
−
1
,
1
=
1
(
2
⌊
log
n
⌋
−
1
×
3
≤
n
)
dp_{1,\lfloor \log n \rfloor-1,\ 1}=1(2^{\lfloor \log n \rfloor-1} \times 3 \le n)
dp1,⌊logn⌋−1,1=1(2⌊logn⌋−1×3≤n)
答案: d p n , 0 , 0 dp_{n,0,0} dpn,0,0。
總時間複雜度 O ( n log 2 n ) O(n \log_2 n) O(nlog2n)。
Summary
本題考查了性質挖掘能力與基本的 d p dp dp能力,可以說是一道不可多得的好題。
我在做這題的時候,性質全部挖掘了出來,可是 d p dp dp的狀態設計卻並不是最優,這導致我列出了一個關於下降冪的狀態轉移,從而時間複雜度多出了一個 n n n。所以說,我是不是很菜?
這還用問……
Code
①在實現的時候,你必須採用滾動陣列,否則空間開不下。
②一些卡常技巧: 少取模,開
O
2
O2
O2。
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int g1=25,g2=25,mod=1e9+7;
int n,L2,L3;
int dp[2][g1][2],bin[g1],sea[g2];
int get_log_two(int tmp){
int p=1,cnt=0;
while (p<=tmp) p*=2,cnt++;
return cnt-1;
}
int get_log_three(int tmp){
int p=1,cnt=0;
while (p<=tmp) p*=3,cnt++;
return cnt-1;
}
int add(int tmpx,int tmpy){return (tmpx+(max(tmpy,0ll)))%mod;}
void init(){
bin[0]=1,sea[0]=1;
for (int i=1;i<=20;i++) bin[i]=(bin[i-1]*2)%mod;
for (int i=1;i<=13;i++) sea[i]=(sea[i-1]*3)%mod;
}
signed main(){
cin>>n;init();
L2=get_log_two(n),L3=get_log_three(n);
dp[0][L2][0]=1;
if (((1ll<<L2)/2)*3<=n) dp[0][L2-1][1]=1;
for (int i=2;i<=n;i++){
for (int j=0;j<=L2;j++){
for (int k=0;k<2;k++){
int p=bin[j]*sea[k];
if (p>n) continue;
dp[1][j][k]=add(dp[1][j][k],((n/p)-i+1)*dp[0][j][k]);
if (j<L2) dp[1][j][k]=add(dp[1][j][k],((n/p)-(n/(2*p)))*dp[0][j+1][k]);
if (k==0) dp[1][j][k]=add(dp[1][j][k],((n/p)-(n/(3*p)))*dp[0][j][k+1]);
}
}
for (int j=0;j<=L2;j++){
for (int k=0;k<2;k++) dp[0][j][k]=dp[1][j][k],dp[1][j][k]=0;
}
}
cout<<dp[0][0][0]<<endl;
return 0;
}