Codeforces 1603D - Artistic Partition(莫反+线段树优化 dp)

Codeforces 题面传送门 & 洛谷题面传送门

学 whk 时比较无聊开了道题做做发现是道神题(

介绍一种不太一样的做法,不观察出决策单调性也可以做。

首先一个很 trivial 的 observation 是,如果 \(2^{k-1}>n\)​ 那么答案就是 \(n\)​,因为我们可以第 \(i\) 段放 \(2^{i-1}\) 个数(最后一段除外),这样每一段中,肯定只有形如 \((x,x)\) 的整数对会产生贡献,这样答案刚好取到下界 \(n\)

我们设 \(dp_{i,j}\) 表示前 \(i\) 个数分成 \(j\) 段,它们的 \(f\) 值之和的最小值,显然 \(j\) 的上界只用取到 \(18\),因此我们计算出 \(dp_{i,j}\) 之后即可 \(\Theta(1)\) 地求出答案。至此,问题转化为如何求 \(dp_{i,j}\)

考虑化简 \(c(l,r)\) 的表达式,一个非常浅显的性质是对于 \(\min(i,j)<l\) 的数对 \((i,j)\),必然有 \(\gcd(i,j)<l\),因此我们可以直接那拿总数 \(\dfrac{r(r+1)}{2}\) 减去 \(\gcd(i,j)\) 小于 \(l\) 的数对 \((i,j)\) 的个数,即如果我们设 \(g(n,k)=\sum\limits_{i=1}^n\sum\limits_{j=i}^n[\gcd(i,j)\le k]\),那么有 \(dp_{i,j}=\min\limits_{k}dp_{k,j-1}+\dfrac{i(i+1)}{2}-g(i,k)\)。而根据莫比乌斯反演的知识,有

\[\begin{aligned} g(n,k)&=\sum\limits_{i=1}^n\sum\limits_{j=1}^n[\gcd(i,j)\le k]\\ &=\sum\limits_{d=1}^k\sum\limits_{i=1}^n\sum\limits_{j=i}^n[\gcd(i,j)=d]\\ &=\sum\limits_{d=1}^k\sum\limits_{i=1}^{n/d}\sum\limits_{j=i}^{n/d}[\gcd(i,j)=1]\\ &=\sum\limits_{d=1}^k\dfrac{1+\sum\limits_{p=1}^{n/d}\mu(p)\lfloor\dfrac{n}{dp}\rfloor^2}{2} \end{aligned} \]

考虑怎样维护之,也就是说怎样动态地对于一个决策点 \(k\),用个什么东西维护 \(dp_{k,j-1}-g(i,k)\) 的值,进而求出全局最小值。考虑线段树优化 dp。我们建立一棵线段树,并且希望,当我们动态地枚举到 \(i\) 时,线段树上下标为 \(k\) 的位置上的值就等于 \(dp_{k,j-1}-g(i,k)\),那么就有 \(dp_{i,j}=\dfrac{i(i+1)}{2}+\text{此时线段树上全局最小值}\)

这又该怎样维护呢?我们发现这样一个性质:当 \(n\)\(n-1\) 变到 \(n\) 时,只有那些 \(d\mid n\)\(\sum\limits_{p=1}^{n/d}\mu(p)\lfloor\dfrac{n}{dp}\rfloor^2\) 会发生改变,也就是说,对于一个固定的 \(k\),如果我们预处理出了 \(sum_i=\sum\limits_{i=1}^n\mu(i)\lfloor\dfrac{n}{i}\rfloor\),那么我们可以在 \(\Theta(d(n))\) 的时间内计算出 \(g(n,k)-g(n-1,k)\)。具体方法就是,遍历所有 \(d\mid n\),如果 \(d\le k\),那么就会 \(g(n,k)\) 就会相较于 \(g(n-1,k)\) 产生 \(\dfrac{1+\sum\limits_{p=1}^{n/d}\mu(p)\lfloor\dfrac{n}{dp}\rfloor^2}{2}-\dfrac{1+\sum\limits_{p=1}^{(n-1)/d}\mu(p)\lfloor\dfrac{n-1}{dp}\rfloor^2}{2}\) 的增量。发现了这个性质以后就很好维护了,当我们扫描到 \(i\) 时遍历一遍所有 \(i\) 的因子 \(d\),计算出 \(\dfrac{1+\sum\limits_{p=1}^{i/d}\mu(p)\lfloor\dfrac{i}{dp}\rfloor^2}{2}-\dfrac{1+\sum\limits_{p=1}^{(i-1)/d}\mu(p)\lfloor\dfrac{i-1}{dp}\rfloor^2}{2}\),然后在线段树上执行一遍后缀减即可。

时间复杂度 \(n\ln n\log^2n\),较为卡常。

代码
using namespace fastio;
const int MAXV = 1e5;
const int LOG_N = 18;
const int MAXC = MAXV << 4;
int pr[MAXV / 6 + 5], mu[MAXV + 5], prcnt = 0;
int smu[MAXV + 5]; ll sm[MAXV + 5];
bool vis[MAXV + 5];
int hd[MAXV + 5], val[MAXC + 5], nxt[MAXC + 5], item_n = 0;
void ins(int x, int y) {val[++item_n] = y; nxt[item_n] = hd[x]; hd[x] = item_n;}
void sieve(int n) {
	mu[1] = 1;
	for (int i = 2; i <= n; i++) {
		if (!vis[i]) mu[i] = -1, pr[++prcnt] = i;
		for (int j = 1; j <= prcnt && pr[j] * i <= n; j++) {
			vis[pr[j] * i] = 1; if (i % pr[j] == 0) break;
			mu[pr[j] * i] = -mu[i];
		}
	}
	for (int i = 1; i <= n; i++)
		for (int j = i; j <= n; j += i)
			ins(j, i);
	for (int i = 1; i <= n; i++) smu[i] = smu[i - 1] + mu[i];
	for (int i = 1; i <= n; i++) {
		sm[i] = sm[i - 1];
		for (int e = hd[i]; e; e = nxt[e]) {
			int f = val[e];
			sm[i] -= 1ll * mu[f] * (i / f - 1) * (i / f - 1);
			sm[i] += 1ll * mu[f] * (i / f) * (i / f);
		}
	}
}
ll dp[LOG_N + 2][MAXV + 5];
struct node {ll mn, lz;} s[MAXV * 4 + 5];
void pushup(int k) {s[k].mn = min(s[k << 1].mn, s[k << 1 | 1].mn) + s[k].lz;}
void build(int k, int l, int r, int lv) {
	s[k].lz = 0; if (l == r) return s[k].mn = 2 * dp[lv][l], void();
	int mid = l + r >> 1; build(k << 1, l, mid, lv); build(k << 1 | 1, mid + 1, r, lv);
	pushup(k);
}
void tag(int k, ll v) {s[k].lz += v; s[k].mn += v;}
void modify(int k, int l, int r, int p, ll v) {
	if (l >= p) return tag(k, v), void();
	int mid = l + r >> 1;
	if (p <= mid) modify(k << 1, l, mid, p, v), tag(k << 1 | 1, v);
	else modify(k << 1 | 1, mid + 1, r, p, v);
	pushup(k);
}
int main() {
	sieve(MAXV); memset(dp, 0x1f, sizeof(dp)); dp[0][0] = 0;
	for (int i = 1; i <= MAXV; i++) dp[1][i] = 1ll * i * (i + 1) / 2;
	for (int i = 2; i <= LOG_N; i++) {
		build(1, 0, MAXV, i - 1);
		for (int j = 1; j <= MAXV; j++) {
			for (int e = hd[j]; e; e = nxt[e]) {
				int f = val[e];
				ll ori = ((j / f == 1) ? 0 : 1) + sm[j / f - 1];
				ll cur = 1 + sm[j / f];
				modify(1, 0, MAXV, f, ori - cur);
			}
			dp[i][j] = ((s[1].mn + s[1].lz) / 2) + 1ll * j * (j + 1) / 2;
//			printf("%d %d %lld\n", i, j, dp[i][j]);
		}
	}
	int qu; read(qu);
	while (qu--) {
		int n, k; read(n); read(k);
		if (k > LOG_N) print(n, '\n');
		else print(dp[k][n], '\n');
	}
	print_final();
	return 0;
}
posted @ 2021-11-25 18:33  tzc_wk  阅读(92)  评论(1)    收藏  举报