zoukankan      html  css  js  c++  java
  • ABC133F Small Products

    考虑 DP。

    状态

    令 $f[ell][x]$ 表示长度为 $ell$,首项不超过 $x$ 的序列的个数。
    答案是 $f[K][N]$。
    有递推 $f[ell][x] = f[ell][x - 1] + f[ell - 1][floor{N/x}]$。照这个递推式求解,复杂度度太高;把它改成
    $f[ell][x] = sum_{y = 1}^{x} f[ell - 1][floor{N/y}]$ 也就是枚举首项。
    我们的目标是求出 $f[K][N]$,结合递推式来看,可以发现我们需要计算的状态的第二维都可以写成 $floor{N/i}$。而我们熟知 $floor{N/i}$ 的不同取值不超过 $2 sqrt{N}$ 个。因此需要计算的状态不超过 $2Ksqrt{N}$ 个。

    先来解决状态表示的问题,也就是 $floor{N/i}$ 的表示问题。虽然 $floor{N/i}$ 的取值不超过 $2sqrt{N}$ 个,但是不能直接以 $floor{N/i}$ 作为数组下标。可以这样做,对于 $color{blue}{ i le sqrt{N} }$,用 $i$ 表示 $floor{N/i}$,对于 $color{red}{ i ge sqrt{N} }$,直接以 $floor{N/i}$ 作为下标。从代码实现的角度说就是开两个数组,$f_1[1..K][1..floor{sqrt N}], f_2[1..K][1..floor{sqrt N}]$,$f_1[ell][i] := f[ell][i]$,$f_2[ell][i] := f[ell][floor{N/i}]$。

    注①:当 $N$ 是完全平方数时,$i le sqrt N$ 与 $i ge sqrt N$ 这两段中都含有 $sqrt{N}$,这并不会造成问题。实际上分段时两侧都取等号是有意为之,这样可以使得递推式更简洁并且没有 corner case。这种分段方法适用于许多跟 $floor{N/i}$ 相关的分块问题。

    注②:关于上一段所说的“对于 $i le sqrt N$,用 $i$ 表示 $floor{N/i}$”,我们不需要关心 $i mapsto floor{N/i}$ 是不是单射。这里所谓“表示 $floor{N/i}$”是说设计一种方法来把所有需要计算的 $f[ell][floor{N/i}]$ 紧凑地存到数组里并且可以快速地由 $ell, i$ 这两个 key 查到 $f[ell][floor{N/i}]$ 的值。不过可以证明,对于 $i le sqrt{N}$,$i mapsto floor{N/i}$ 确实是单射。

    递推

    对于 $f_1$,有递推
    $f_1[l][x] = f_1[l][x - 1] + f[l - 1][floor{N/x}]$
    由于 $1 le x le floor{sqrt{N}}$,有 $f[l - 1][floor{N/x}] = f_2[l-1][x]$,从而有
    $f_1[l][x] = f_1[l][x - 1] + f_2[l-1][x]$

    对于 $f_2$,有递推式
    $f_2[l][i] = f_2[l][i+1] + sum_{x=floor{N/(i+1)} + 1}^{floor{N/i}} f[l -1][floor{N/x}] $
    容易证明下列几个不等式

    1. $floor{N/i} ge floor{N/(i + 1)}$
    2. $floor{N/floor{N/i}} ge i$
    3. $floor{N / left(floor{N/i} + 1 ight) } < i$

    只证第 3 个。
    设 $ floor{frac{N}{i}} = t$,我们有
    $ t le frac{N}{i} < t + 1 iff it le N < i(t + 1) iff ifrac{t}{t + 1} le frac{N}{t + 1} < i implies floor{frac{N}{t + 1}} < i$

    因此我们有 $i le floor{frac{N}{x}} < i + 1$,即对于 $floor{frac{N}{i + 1}} < x le floor{frac Ni}$ 恒有 $ floor{frac{N}{x}} = i $,这里我们得到一个很有用的等式

    若 $floor{frac{N}{i}} ge floor{frac{N}{i+1}}$,则
    $f[l][floor{frac{N}{i}}] = f[l][floor{N/(i + 1)}] + left( floor{frac{N}{i}} - floor{frac{N}{i+1}} ight) f[l - 1][i] $
    并且当 $floor{frac{N}{i}} > floor{frac{N}{i+1}}$ 时,$i$ 可表为 $floor{ frac{N}{ floor{ frac{N}{i} } } }$

    从而有
    egin{aligned}
    f_2[l][i] &= f_2[l][i+1] + left( floor{frac{N}{i}} - floor{frac{N}{i+1}} ight) f[l - 1][i] \
    &= f_2[l][i+1] + left( floor{frac{N}{i}} - floor{frac{N}{i+1}} ight) f_1[l - 1][i]
    end{aligned}

    $f_2$ 的边界条件有两个:
    1.
    $f_2[1][i] = floor{ frac{N}{i} } $
    2.
    egin{aligned} f_2[l][floor{sqrt{N}}] &:= f[l][floor{frac{N}{floor{sqrt N}}}] \
    &= f[l][floor{frac{N}{floor{sqrt N}+1}}] + left( floor{ frac{N}{ floor{sqrt{N}} } } - floor{ frac{N}{floor{sqrt N}+1} } ight) f[l - 1][floor{ sqrt{N} } ] \
    &= f_1[l][floor{frac{N}{floor{sqrt N}+1}}] + left(floor{frac{N}{floor{sqrt{N}}}} - floor{frac{N}{floor{sqrt{N}} + 1}} ight) f_1[l - 1][floor{ sqrt{N} } ]
    end{aligned}

    代码

    int main() {
    
        int n, k;
        scan(n, k);
        int r = sqrt(n + 0.5); // r is defined to be floor(sqrt{n})
        vv<int> f1(k + 1, vi(r + 1)); // f1[len][i]:长为len,首项 <= i
        vv<int> f2(k + 1, vi(r + 1)); // f2[len][i]:长为len,首项 <= n/i
     
        up (i, 1, r) {
            f1[1][i]=i;
        }
        up (i, 1, r) {
            f2[1][i] = n / i;
        }
        up (l, 2, k) {
            up (i, 1, r) {
                f1[l][i] = f1[l][i - 1] + f2[l - 1][i];
                if (f1[l][i] >= mod) {
                    f1[l][i] -= mod;
                }
            }
            f2[l][r] = f1[l][n/(r + 1)] + (ll)(n / r - (n / (r + 1))) * f1[l - 1][r] % mod;
            if (f2[l][r] >= mod) {
                f2[l][r] -= mod;
            }
            down (i, r - 1, 1) {
                f2[l][i] = f2[l][i + 1] + (ll)(n / i - (n / (i + 1))) * f1[l - 1][i] % mod;
                if (f2[l][i] >= mod) {
                    f2[l][i] -= mod;
                }
            }
        }
        println(f2[k][1]);
     
        return 0;
    }
    

    从另一个角度看待这个问题。以下所有 / 运算都向下取整。
    取一个数字 m,求出 f[L][1..m]
    f[L][i] = f[L][i-1] + f[L-1][N/i]
    开一个数组 g[1..m],g[L][i] := f[L][N/i]
    问题归结为如何计算 g[L][i]
    上面已经得到
    g[L][i] = g[L][i+1] + (L/i - L/(i+1))*f[L-1][i]
    整个计算过程如下
    for i = 1 to m
    f[1][i] = i
    g[1][i] = N/i
    for L = 1 to K
    f[L][0] = 0
    for L = 2 to K
    for i = 1 to m
    f[L][i] = f[L][i-1] + g[L-1][i]
    // compute g[L][m]
    for i = m - 1 down to 1
    g[L][i] = g[L][i+1] + (L/i - L/(i+1)) * f[L-1][i]
    问题进一步归结为如何计算 g[L][m],即 f[L][N/m]
    若 N/m <= m 则 f[L][N/m] 已经算出来了,不成问题。
    若 N/m > m 但 N/(m + 1) <= m 则 f[L][N/m] = f[L][N/(m+1)] + (N/m - (N/(m+1))*f[L-1][m],也不成问题。
    所以保险的办法是取 m 使得 N/(m + 1) <= m,取 m = floor(sqrt(N)) 就可以保证 N/(m+1) <= m。证明:m+1 > sqrt(N) 因此 N/(m+1) < sqrt(n) <= m 。
    取 m = floor(sqrt(N)) + 1 可以保证 N / m < m。证明 m > sqrt(N),所以 N / m < sqrt(N) <= floor(sqrt(N)) < m。

  • 相关阅读:
    Qt开发技术:QCharts(二)QCharts折线图介绍、Demo以及代码详解
    OpenCV开发笔记(六十八):红胖子8分钟带你使用特征点Flann最邻近差值匹配识别(图文并茂+浅显易懂+程序源码)
    keepalived+MySQL实现高可用
    使用ProxySQL实现MySQL Group Replication的故障转移、读写分离(二)
    使用ProxySQL实现MySQL Group Replication的故障转移、读写分离(一)
    Oracle Dataguard故障转移(failover)操作
    Oracle DataGuard故障转移(failover)后使用RMAN还原失败的主库
    MySQL组复制MGR(四)-- 单主模式与多主模式
    MySQL组复制MGR(三)-- 组复制监控
    MySQL组复制MGR(二)-- 组复制搭建
  • 原文地址:https://www.cnblogs.com/Patt/p/11788010.html
Copyright © 2011-2022 走看看