Codeforces 1603D. Artistic Partition

发布时间 2023-06-23 21:27:02作者: DeaphetS

题目链接:D - Artistic Partition

题目大意:要求将 \([1,n]\) 分成 \(k\) 段,使得每段对应的 \(c(l,r)\) 之和最小,其中 \(c(l,r)=\sum_{i=l}^r\sum_{j=i}^r[\gcd(i,j)\ge l]\)

首先注意到当 \(r<2l\) 时,\(c(l,r)=r-l+1\)。所以当 \(2^k-1\ge n\) 时答案即为 \(n\)

考虑 \(\texttt{DP}\),设 \(f_{i,j}\) 表示已经分了 \(i\) 段,段末为 \(j\) 的最小值,则转移式为 \(f_{i,j}=\min\{f_{i-1,k}+c(k+1,j)\}\)

考虑转移过程中,每次 \(j-1\)\(j\) 对每个 \(f_{i-1,k}+c(k+1,j)\) 造成的影响。需要快速维护每个 \(k\) 对应值的变化量,即 \(c(k+1,j)-c(k+1,j-1)\),显然这个值为 $\sum_{i=k+1}^{j} [\gcd(i,j)\ge k+1] $。

由于 \(i<j\) 时,\(\gcd(i,j)\le i\),于是就相当于要对每个位置 \(k\) 加上 $\sum_{i=1}^{j} [\gcd(i,j)\ge k+1] $。进一步地,其实就是加上 \(j-\sum_{i=1}^{j} [\gcd(i,j)\le k]\)。众所周知,\(\gcd(i,j)\) 的取值只可能是 \(j\) 的约数,那么把 \(j\) 的所有约数 \(d_i\) 求出来,每次将区间 \([d_{i},d_{i+1})\) 加上对应贡献即可。初始贡献为 \(j\),当 \(i\) 每次加一时贡献会减少 \(\varphi(\frac{j}{d_i})\),这是与 \(j\)\(\gcd\) 的值为 \(d_i\) 的方案数。处理完每个位置的变化量后,当前的全局最小值即为 \(f_{i,j}\) 的值。

时间复杂度方面,相当于在每一层转移中,对于每对 \((j,d_i)\) 都要进行一次线段树的区间加操作,那么显然是一个 \(O(n\log^3n)\) 的复杂度。于是为了卡常,需要将这个区间加变成后缀加才可通过此题。

#include<bits/stdc++.h>
using namespace std;
#define N 100010
#define ls o<<1
#define rs o<<1|1
#define LL long long
int T,n,k,cnt,p[N],v[N],phi[N];
LL f[20][N];
vector<int>d[N];
struct rua
{
	LL tag,mn;
}t[N<<2];
void Build(int o,int l,int r,int i)
{
	t[o].tag=0;
	if(l==r){
		t[o].mn=f[i][l];
		return;
	}
	int mid=l+r>>1;
	Build(ls,l,mid,i);
	Build(rs,mid+1,r,i);
	t[o].mn=min(t[ls].mn,t[rs].mn);
}
void add(int o,int tl,int tr,int l,int r,int c)
{
	if(l<=tl && tr<=r){
		t[o].tag+=c;
		t[o].mn+=c;
		return;
	}
	int mid=tl+tr>>1;
	if(l<=mid)add(ls,tl,mid,l,r,c),t[rs].tag+=c,t[rs].mn+=c;
	else add(rs,mid+1,tr,l,r,c);
	t[o].mn=min(t[ls].mn,t[rs].mn)+t[o].tag;
}
void pretype()
{
	phi[1]=1;
	for(int i=2;i<N;i++){
		if(!v[i])p[++cnt]=i,phi[i]=i-1;
		for(int j=1;j<=cnt && i*p[j]<N;j++){
			v[i*p[j]]=1;
			if(i%p[j]==0){
				phi[i*p[j]]=phi[i]*p[j];
				break;
			}
			phi[i*p[j]]=phi[i]*phi[p[j]];
		}
	}
	for(int i=1;i<N;i++)
	for(int j=i;j<N;j+=i)
		d[j].push_back(i);
	memset(f,0x3f,sizeof(f));
	for(int i=1;i<N;i++)f[1][i]=1ll*i*(i+1)/2;
	for(int i=2;i<=17;i++){
		LL v=0;
		Build(1,1,N-1,i-1);
		for(int j=1;j<N;j++){
			v+=j;
			for(auto k:d[j])add(1,1,N-1,k,N-1,-phi[j/k]);
			f[i][j]=v+t[1].mn;
		}
	}
}
int main()
{
	pretype();
	scanf("%d",&T);
	while(T--){
		scanf("%d%d",&n,&k);
		if(k>20 || (1<<k)-1>=n)printf("%d\n",n);
		else printf("%lld\n",f[k][n]);
	}
}