没有名字 [整除分块优化dp]

没 有 名 字 没有名字


正 解 部 分 \color{red}{正解部分}

F [ i , j ] F[i, j] F[i,j] 表示 i i i 位置填 j j j 满足条件的方案数, 则 F [ i , j ] = ∑ k = 1 ⌊ M j ⌋ F [ i − 1 , k ] F[i, j] = \sum\limits_{k=1}^{\lfloor \frac{M}{j} \rfloor} F[i-1, k] F[i,j]=k=1jMF[i1,k], 直接转移复杂度 O ( N M 2 ) O(NM^2) O(NM2), 不可过 .

观察到 ⌊ M j ⌋ \lfloor \frac{M}{j} \rfloor jM 的取值仅有 O ( M ) O(\sqrt{M}) O(M ) 个, 且转移是前缀和的形式, 因此考虑使用 整除分块前缀和 优化,

F ′ [ i , j ] F'[i, j] F[i,j] 表示 i i i 位置填 整除分块 从大到小 j j j 值所对应的 分母 的方案数, g [ i , j ] g[i, j] g[i,j] 表示 对应 前缀和, 则

     F ′ [ i , j ] = ( r [ j ] − l [ j ] + 1 ) × ∑ k = 1 v a l [ j ] F [ i − 1 , k ] = ( r [ j ] − l [ j ] + 1 ) × ∑ k = 1 M p [ M v a l [ j ] ] ] F ′ [ i − 1 , k ] = ( r [ j ] − l [ j ] + 1 ) × g [ i − 1 , M p [ M v a l [ j ] ] ]   ] \begin{aligned} &\ \ \ \ F'[i, j] \\ & = (r[j]-l[j]+1)\times \sum\limits_{k=1}^{val[j]} F[i-1, k] \\ & = (r[j]-l[j]+1) \times \sum\limits_{k=1}^{Mp[\frac{M}{val[j]]}]} F'[i-1,k] \\ & = (r[j]-l[j]+1)\times g[i-1, Mp[\frac{M}{val[j]]}]\ ]\end{aligned}     F[i,j]=(r[j]l[j]+1)×k=1val[j]F[i1,k]=(r[j]l[j]+1)×k=1Mp[val[j]]M]F[i1,k]=(r[j]l[j]+1)×g[i1,Mp[val[j]]M] ]

最后 a n s = g [ N , c n t ] ans = g[N, cnt] ans=g[N,cnt], 使用 std::map<int, int> 时间复杂度 O ( N M log ⁡ M ) O(N \sqrt{M} \log M) O(NM logM) .

c n t cnt cnt 为取值的总个数, 整除分块的值从前往后单调不增 : M M/2 M/3 M/3 M/4 M/4 …
M p [ x ] Mp[x] Mp[x] x x x 对应的块的编号 .
v a l [ i ] val[i] val[i] 表示编号为 i i i 的块对应的值 .

但是 M p [ x ] Mp[x] Mp[x] 直接使用 std::map<int,int> 储存会 T L E TLE TLE,
观察到在 从小到大 枚举 j j j 时, ⌊ M v a l [ j ] ⌋ = ⌊ M M l [ j ] ⌋ \lfloor \frac{M}{val[j]} \rfloor = \lfloor \frac{M}{\frac{M}{l[j]}} \rfloor val[j]M=l[j]MM 的值是 单调不增 的, 且只会变化 M \sqrt{M} M 次, 因此可以使用指针维护 .

时间复杂度 O ( N M ) O(N \sqrt{M}) O(NM ) .


实 现 部 分 \color{red}{实现部分}

#include<bits/stdc++.h>
#define reg register

const int maxn = 1000005;
const int mod = 1e9 + 7;

int N;
int M;
int cnt;

int Mp[maxn];
int val[maxn];
int llim[maxn];
int rlim[maxn];
int F[102][maxn];
int g[102][maxn];

int main(){
        scanf("%d%d", &N, &M);
        for(reg int l = 1, r; l <= M; l = r+1){
                r = M/(M/l), llim[++ cnt] = l, rlim[cnt] = r;
                F[1][cnt] = r-l+1, val[cnt] = M/l;
                g[1][cnt] = (g[1][cnt-1] + F[1][cnt]) % mod;
        }
        for(reg int i = 2; i <= N; i ++){
                int t = cnt;
                for(reg int j = 1; j <= cnt; j ++){
                        while(t >= 1 && M/(M/llim[j]) > val[t]) t --;
                        F[i][j] = (rlim[j]-llim[j]+1)*1ll*g[i-1][t] % mod;
                        g[i][j] = (g[i][j-1] + F[i][j]) % mod;
                }
        }
        printf("%d\n", g[N][cnt]);
        return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值