题目大致就是对于
1
n
1~n
1n每个位置都可以转移到比他小的位置,两种转换方式,一种是直接转移
1
−
(
n
−
1
)
1 -( n-1)
1−(n−1),一种是转移到
⌊
n
z
⌋
,
z
∈
[
2
,
n
]
\lfloor \cfrac{n}{z}\rfloor,z \in [2,n]
⌊zn⌋,z∈[2,n],有n个位置,总有有多少种方案,答案取模m
算法思路
既然涉及到向下取整,又有区间,这不很显然?整除分块!!
dp的思想来看,
1
−
(
x
−
1
)
1-(x-1)
1−(x−1)的任何一个状态都可以转移到
x
x
x,因为是线性递推的,故我们可以使用一个前缀和数组sum[]来记录这个
对于第二种转移方式,就是整除,不过值得注意的是,
z
∈
[
2
,
x
]
z \in [2,x]
z∈[2,x],所以我们整除分块
l
l
l的初始值应该为
2
2
2
知道了
l
l
l,就可以通过性质推出
r
=
i
⌊
i
l
⌋
r=\cfrac{i}{\lfloor \cfrac{i}{l} \rfloor}
r=⌊li⌋i
对于一整块区间
i
∈
[
l
,
r
]
i \in [l,r]
i∈[l,r],他们
⌊
n
i
⌋
\lfloor \cfrac{n}{i} \rfloor
⌊in⌋的值都是一样的,所以这一整段区间的值,都可以由
d
p
[
n
/
i
]
dp[n/i]
dp[n/i]转移过来
故,我们可以得到状态转移方程为
d
p
[
x
]
=
∑
i
=
1
x
−
1
d
p
[
i
]
+
∑
d
p
[
⌊
x
l
⌋
]
dp[x]=\sum_{i = 1}^{x-1}dp[i]+\sum dp[\lfloor \cfrac{x}{l} \rfloor]
dp[x]=∑i=1x−1dp[i]+∑dp[⌊lx⌋]
#pragmaGCC optimize("O3")#include<bits/stdc++.h>#include<unordered_map>#include<unordered_set>usingnamespace std;#definedebug(x) cerr << #x <<": "<< x <<'\n'#definebdcerr <<"----------------------"<< el#defineel'\n'#defineclputchar('\n')#definepbpush_back#defineebemplace_back#definexfirst#defineysecond#definerep(i, a, b)for(int i =(a); i <=(b); i++)#defineloop(i, a, b)for(int i =(a); i <(b); i++)#definedwn(i, a, b)for(int i =(a); i >=(b); i--)#defineceil(a, b)(a +(b -1))/ b#definems(a, x)memset(a, x,sizeof(a))#defineinf0x3f3f3f3f#definedbdoubletypedeflonglong LL;typedeflongdouble LD;typedef pair<int,int> PII;typedef pair<db, db> PDD;typedef vector<int> vci;typedef map<int,int> mii;typedef mii::iterator mii_it;constint N =2e6+2, M =2e6+10, E =1e3+10, md =1e9+7;constdouble PI =acos(-1), eps =1e-8;// int T, n, m;
LL ans, n, m, dp[N], suf[N], f[N], sum[N];intmain(){
cin.tie(0);
cout.tie(0);
cin >> n >> m;
f[1]= sum[1]=1;rep(i,2, n){
f[i]=(sum[i -1]+ f[i])% m;for(int l =2, r; l <= i; l = r +1){
r = i /(i / l);
f[i]=(f[i]+(r - l +1)* f[i / l])% m;}
sum[i]=(sum[i -1]+ f[i])% m;}
cout << f[n]<<el;// dp[n] = 1; 逆序做法// dwn(i, n, 1)// {// dp[i] = dp[i] + suf[i + 1];// dp[i] %= m;// suf[i] = dp[i] + suf[i + 1];//suf代表后缀和// suf[i] %= m;// for (int l = 2, r; l <= i; l = r + 1)//代表选取的z// {// r = i / (i / l);// dp[i / l] = (dp[i / l] + (r - l + 1) * dp[i]) % m;// }// }// cout << dp[1] << el;}
对于一个数
i
i
i,那么某种倍数
j
j
j,会让
[
i
×
j
,
i
×
j
+
j
)
[i \times j, i\times j+j)
[i×j,i×j+j)的长条都移动到
i
i
i位置
当然,考虑边界
j
∗
i
<
=
n
j * i <=n
j∗i<=n且
j
∗
i
+
j
≤
n
+
1
j * i + j \leq n + 1
j∗i+j≤n+1
注意此处不是
j
∗
i
+
i
j * i + i
j∗i+i而是
j
∗
i
+
j
j *i + j
j∗i+j,因为对于
x
x
x来说的话,这个区间内的一个数是
k
k
k,则
⌊
k
i
⌋
=
j
\lfloor \cfrac{k}{i}\rfloor=j
⌊ik⌋=j,所以区间长度应该是
j
j
j
枚举倍数的时间复杂度是
O
(
log
n
)
O(\log n)
O(logn),所以总得时间复杂度为
O
(
n
log
n
)
O(n \log n)
O(nlogn)
即状态转移方程为
代码实现
#pragmaGCC optimize("O3")#include<bits/stdc++.h>#include<unordered_map>#include<unordered_set>usingnamespace std;#definedebug(x) cerr << #x <<": "<< x <<'\n'#definebdcerr <<"----------------------"<< el#defineel'\n'#defineclputchar('\n')#definepbpush_back#defineebemplace_back#definexfirst#defineysecond#definerep(i, a, b)for(int i =(a); i <=(b); i++)#defineloop(i, a, b)for(int i =(a); i <(b); i++)#definedwn(i, a, b)for(int i =(a); i >=(b); i--)#defineceil(a, b)(a +(b -1))/ b#definems(a, x)memset(a, x,sizeof(a))#defineinf0x3f3f3f3f#definedbdoubletypedeflonglong LL;typedeflongdouble LD;typedef pair<int,int> PII;typedef pair<db, db> PDD;typedef vector<int> vci;typedef map<int,int> mii;typedef mii::iterator mii_it;constint N =4e6+10, M =2e6+10, E =1e3+10;constdouble PI =acos(-1), eps =1e-8;
LL md =1e9+7;int T, n, m;
LL suf[N], f[N];intmain(){
cin.tie(0);
cout.tie(0);
cin >> n >> md;
f[n]= suf[n]=1;dwn(i, n -1,1)//复杂度为调和级数{
f[i]= suf[i +1];//操作一的贡献for(int j =2; j * i <= n; j ++){
f[i]=(f[i]+ suf[j * i]- suf[min(j * i + j, n +1)])% md;//区间[j * i, j * i + j - 1]的贡献,操作二}
suf[i]=(suf[i +1]+ f[i])% md;}
cout << f[1]<< el;}