CF1603D Artistic Partition

fi,kf_{i,k} 表示前 ii 个数分成 kk 段的最小值。

转移方程:

fi,k=minj=1i{fj1,k1+c(j,i)}f_{i,k}=\min_{j=1}^{i}\{f_{j-1,k-1}+c(j,i)\}

观察得:如果 2k1>n2^{k-1}>n,考虑在第 ii 段放 2i12^{i-1} 个数,只有 (x,x)(x,x) 的整数对会产生贡献,那么答案即为 nn,故 kk 上界为 1818。可以预处理 ff 数组,后 O(1)\mathcal O(1) 回答询问。

考虑到 \ge 有点难处理,故设 g(n,k)=i=1nj=1n[gcd(i,j)k]g(n,k)=\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{n}[\gcd(i,j)\leq k],则 c(n,k)=n(n+1)2g(n,k)c(n,k)=\frac{n(n+1)}{2}-g(n,k)fi,k=minj=1i(fj1,k1+i(i+1)2g(j,i))f_{i,k}=\min\limits_{j=1}^{i}(f_{j-1,k-1}+\frac{i(i+1)}{2}-g(j,i))

考虑使用线段树优化 min\min,在 O(logn)\mathcal O(\log n) 的时间内找到最小的 fj1k1+g(j,i)f_{j-1}{k-1}+g(j,i)

再根据莫反知识,有:

g(n,k)=i=1nj=1n[gcd(i,j)k]=d=1ki=1nj=in[gcd(i,j)=d]=d=1ki=1ndj=ind[gcd(i,j)=1]=d=1k1+p=1ndμ(p)ndp22\begin{aligned} g(n,k)&=\sum_{i=1}^{n}\sum_{j=1}^{n}[\gcd(i,j)\leq k]\\ &=\sum_{d=1}^{k}\sum_{i=1}^{n}\sum_{j=i}^{n}[\gcd(i,j)=d]\\ &=\sum_{d=1}^{k}\sum_{i=1}^{\lfloor\frac{n}{d}\rfloor}\sum_{j=i}^{\lfloor\frac{n}{d}\rfloor}[\gcd(i,j)=1]\\ &=\sum_{d=1}^{k}\frac{1+\sum\limits_{p=1}^{\lfloor\frac{n}{d}\rfloor}\mu(p)\lfloor\frac{n}{dp}\rfloor^2}{2} \end{aligned}

考虑怎样维护,我们发现当 nnn1n-1 变到 nn 时,只有那些 dnd|np=1nd\sum\limits_{p=1}^{\lfloor\frac{n}{d}\rfloor} 会发生改变,故预处理因子维护增量即可。

时间复杂度 O(nlnnlog2n)\mathcal O(n \ln n \log^2 n)

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;

const int _ = 1e5 + 5;

int pr[_ / 6], mu[_], cnt, smu[_];

ll sm[_];

bool vis[_];

vector<int> d[_ << 1];

inline void solve(int n)
{
	mu[1] = 1;
	for(int i = 2; i <= n; ++i)
	{
		if(!vis[i]) mu[i] = -1, pr[++cnt] = i;
		for(int j = 1; j <= cnt && 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) d[j].push_back(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 f : d[i])
		{
			sm[i] -= 1ll * mu[f] * (i / f - 1) * (i / f - 1);
			sm[i] += 1ll * mu[f] * (i / f) * (i / f);
		}
	}
}

ll f[20][_];

struct Node
{
	ll mn, lz;
} s[_ << 2];

void build(int o, int l, int r, int lv)
{
	s[o].lz = 0;
	if(l == r)
	{
		s[o].mn = 2 * f[lv][l];
		return;
	}
	int mid = (l + r) >> 1;
	build(o << 1, l, mid, lv);
	build(o << 1 | 1, mid + 1, r, lv);
	s[o].mn = min(s[o << 1].mn, s[o << 1 | 1].mn);
}

inline void tag(int o, ll v)
{
	s[o].lz += v, s[o].mn += v;
}

void update(int o, int l, int r, int p, ll v)
{
	if(p <= l)
	{
		tag(o, v);
		return;
	}
	int mid = (l + r) >> 1;
	if(p <= mid)
		update(o << 1, l, mid, p, v), tag(o << 1 | 1, v);
	else
		update(o << 1 | 1, mid + 1, r, p, v);
	s[o].mn = min(s[o << 1].mn, s[o << 1 | 1].mn) + s[o].lz;
}

signed main()
{
	solve(_ - 5);
	memset(f, 0x1f, sizeof f);
	f[0][0] = 0;
	for(int i = 1; i <= _ - 5; ++i) f[1][i] = 1ll * i * (i + 1) / 2;
	for(int i = 2; i <= 18; ++i)
	{
		build(1, 0, _ - 5, i - 1);
		for(int j = 1; j <= _ - 5; ++j)
		{
			for(int f : d[j])
			{
				ll a = ((j / f == 1) ? 0 : 1) + sm[j / f - 1];
				ll b = 1 + sm[j / f];
				update(1, 0, _ - 5, f, a - b);
			}
			f[i][j] = ((s[1].mn + s[1].lz) / 2) + 1ll * j * (j + 1) / 2;
		}
	}
	int q, n, k;
	scanf("%d", &q);
	while(q--)
	{
		scanf("%d%d", &n, &k);
		if(k > 18) printf("%d\n", n);
		else printf("%lld\n", f[k][n]);
	}
	return 0;
}
posted @ 2022-11-21 15:10  蒟蒻orz  阅读(7)  评论(0)    收藏  举报  来源