Codeforces 1603D. 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]);
}
}