CF1603D Artistic Partition

这是道有趣的观察性质题,可惜我没有脑子。

看到这个 dp 形式就非常整体二分,所以它就是整体二分(雾

我们先令 \(c(i,j)\) 表示 \(i\le x<y\le j,\gcd(x,y)\ge i\) 的数量,输出时 \(+n\) 即可。\(c(i,j)\) 的值在一些基本的转化后变为 \(\sum\limits_{k=i}^j\sum\limits_{x=1}^k\varphi(k)\),所以 \(n\sqrt{n}\) 预处理后可以 \(O(\log n)\) 计算。

然后发现这个整体二分状态爆炸,而且一时没有通过常规手段的优化方法,就只有减少状态了。

容易注意到 \(c(i,j)\)\(j\ge 2i-1\) 时为 \(0\)。接下来就是一个绝妙的性质,\(f(n,k)\)\(k>\log n\) 时为 \(0\),证明显然。果然 CF 就是要见到 2 倍想 \(\log\)

剩下的就不用多说了。时间复杂度 \(O(n\log^2 n+n\sqrt{n})\)

总结:踩了这么多次坑怎么还是没有把两倍限制和 \(\log\) 联系起来的直觉啊。

#include <cstdio>
#include <algorithm>
#include <cstring>
#define gc (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2) ? EOF : *p1 ++)

char buf[100000], *p1, *p2;
inline int read() {
	char ch;
	int x = 0;
	while ((ch = gc) < 48);
	do x = x * 10 + ch - 48; while ((ch = gc) >= 48);
	return x;
}
typedef long long ll;
ll dp[18][100005], phi[100005], p[100005][680];
int Prime[100005], lft[100005][680], len[100005], cnt;
bool mark[100005];
void init(int n) {
	for (int i = 2; i <= n; ++ i) {
		if (!mark[i]) Prime[++ cnt] = i, phi[i] = i - 1;
		for (int j = 1; i * Prime[j] <= n && j <= cnt; ++ j) {
			mark[i * Prime[j]] = true;
			if (i % Prime[j] == 0) {phi[i * Prime[j]] = phi[i] * Prime[j]; break;}
			phi[i * Prime[j]] = phi[i] * (Prime[j] - 1);
		}
	}
	for (int i = 2; i <= n; ++ i) phi[i] += phi[i - 1];
	for (int i = 1; i <= n; ++ i) {
		for (int l = 1, r; l <= i; l = r + 1) {
			r = i / (i / l);
			lft[i][++ len[i]] = l;
			p[i][len[i]] = 1ll * (r - l + 1) * phi[i / l];
		}
		lft[i][++ len[i]] = i + 1;
		for (int j = len[i] - 1; j; -- j) p[i][j] += p[i][j + 1];
	}
}
inline ll cost(int i, int j) {
	int pos = std::lower_bound(lft[j] + 1, lft[j] + len[j] + 1, i) - lft[j]; 
	return p[j][pos] + 1ll * (lft[j][pos] - i) * phi[j / i];
}
void solve(ll *f, ll *g, int l, int r, int x, int y) {
	if (l > r) return;
	if (x == y) {
		for (int i = l; i <= r; ++ i) g[i] = f[x] + cost(x + 1, i);
		return;
	}
	int mid = l + r >> 1, opt = 0;
	ll now = cost(std::min(y, mid - 1) + 1, mid);
	for (int i = std::min(y, mid - 1); i >= x; -- i) {
		if (g[mid] > f[i] + now) g[mid] = f[i] + now, opt = i;
		now += phi[mid / i];
	}
	solve(f, g, l, mid - 1, x, opt), solve(f, g, mid + 1, r, opt, y);
}

int main() {
	init(100000);
	memset(dp, 0x3f, sizeof dp);
	for (int i = 1; i <= 100000; ++ i) dp[1][i] = cost(1, i);
	for (int i = 2; i <= 17; ++ i) solve(dp[i - 1], dp[i], i, 100000, i - 1, 99999);
	int _ = read();
	while (_ --) {
		int n = read(), k = read();
		if (k > 17) {printf("%d\n", n); continue;}
		printf("%lld\n", dp[k][n] + n);
	}
	return 0;
}
posted @ 2022-08-30 20:37  zqs2020  阅读(35)  评论(0编辑  收藏  举报