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

题面

LOJ #2802. 「CCC 2018」平衡树

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

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

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

题解

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

显然有一个转移:

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

边界为 \(f_1 = 1\)

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

这是因为

\[\lfloor \frac{\lfloor \frac{n}{x} \rfloor } y \rfloor = \lfloor \frac{n}{xy} \rfloor \]

证明是很显然的。

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

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

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

不断递归下去就行了,深度是 \(\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\n", Dp(n));

	return 0;
}
posted @ 2018-08-16 22:06  zjp_shadow  阅读(187)  评论(0编辑  收藏  举报