• LOJ #2802. 「CCC 2018」平衡树(整除分块 + dp)


    题面

    LOJ #2802. 「CCC 2018」平衡树

    题面有点难看。。。请认真阅读理解题意。

    转化后就是,给你一个数 (N) ,每次选择一个 (k in [2, N])(N) 变成 (displaystyle lfloor frac{N}{k} floor) ,到 (1) 停止。

    求一共有多少不同的操作序列,也就是操作次数不一样或者某次操作的 (k) 不相同。

    题解

    首先考虑 dp ,令 (f_i) 为以 (i) 为开头的不同操作序列数。

    显然有一个转移:

    [f_i = sum_{k=2}^{i} f_{lfloor frac{i}{k} floor} ]

    边界为 (f_1 = 1)

    显然这个式子能用整除分块来进行优化,就是对于 (displaystyle lfloor frac{i}{k} floor) 相同一起处理,很容易发现这些 (f_{lfloor frac{i}{k} floor}) 也是相同的。

    这是因为

    [lfloor frac{lfloor frac{n}{x} floor } y floor = lfloor frac{n}{xy} floor ]

    证明是很显然的。

    所以有用的 (f) 总共只有 (sqrt N) 个。

    那么我们记忆化搜索即可,然后用一些对于这个利用整除分块的常用标号的方式。

    也就是 (x< sqrt N)(x)(x ge sqrt N)(displaystyle lfloor frac{N}{x} floor)

    不断递归下去就行了,深度是 (log N) 的。

    然后复杂度?不会证。这是个整除分块套整除分块。。

    著名 OI 选手 zhou888 口胡证明是 (O(N ^ frac{3}{4})) 的。

    总结

    要相信分块套起来的复杂度,然后记忆化的时候最好手写哈希,或者用一些特殊性质,常数能小很多。

    代码

    #include <bits/stdc++.h>
    
    #define For(i, l, r) for(register int i = (l), i##end = (int)(r); i <= i##end; ++i)
    #define Fordown(i, r, l) for(register int i = (r), i##end = (int)(l); i >= i##end; --i)
    #define Set(a, v) memset(a, v, sizeof(a))
    #define Cpy(a, b) memcpy(a, b, sizeof(a))
    #define debug(x) cout << #x << ": " << x << endl
    #define DEBUG(...) fprintf(stderr, __VA_ARGS__)
    
    using namespace std;
    
    inline bool chkmin(int &a, int b) {return b < a ? a = b, 1 : 0;}
    inline bool chkmax(int &a, int b) {return b > a ? a = b, 1 : 0;}
    
    inline int read() {
        int x = 0, fh = 1; char ch = getchar();
        for (; !isdigit(ch); ch = getchar()) if (ch == '-') fh = -1;
        for (; isdigit(ch); ch = getchar()) x = (x << 1) + (x << 3) + (ch ^ 48);
        return x * fh;
    }
    
    void File() {
    #ifdef zjp_shadow
    	freopen ("2802.in", "r", stdin);
    	freopen ("2802.out", "w", stdout);
    #endif
    }
    
    typedef long long ll;
    
    const int Maxn = 1e6 + 1e3;
    
    int n; ll Ans1[Maxn], Ans2[Maxn];
    
    inline void Insert(int pos, ll uv) {
    	if (pos < Maxn) Ans1[pos] = uv; else Ans2[n / pos] = uv;
    }
    
    inline ll Find(int pos) {
    	return pos < Maxn ? Ans1[pos] : Ans2[n / pos];
    }
    
    ll Dp(int val) {
    	if (val == 1) return 1;
    	ll res = Find(val); if (res) return res;
    	for (register int i = 2, Nexti; i <= val; i = Nexti + 1)
    		Nexti = val / (val / i), res += Dp(val / i) * (Nexti - i + 1);
    	Insert(val, res); return res;
    }
    
    int main () {
    
    	File();
    
    	n = read();
    	printf ("%lld
    ", Dp(n));
    
    	return 0;
    }
    
  • 相关阅读:
    windows cmd中查看某个命令所在的路径
    linux vi编辑器中,如何通过快捷键上下翻页?
    linux系统中,查看当前系统中,都在监听哪些端口
    下载mysql server安装包的时候,不登录oracle账号,实现下载
    plsql developer中,清除登录历史
    linux环境下,清空history中记录的历史命令
    Linux下搭建hadoop开发环境-超详细
    HDFS架构详解-非官档
    SSH免密码登录配置方法详解
    bin/hdfs dfs命令存在WARN util.NativeCodeLoader问题消除方法
  • 原文地址:https://www.cnblogs.com/zjp-shadow/p/9490432.html
Copyright © 2020-2023  润新知