[CF1603D]Artistic Partition

Artistic Partition

题解

看到这种统计最小值的题应该很容易想到 d p dp dp,我们可以记 d p i , j dp_{i,j} dpi,j表示划分到点 i i i,已经划分了 j j j块,但如果直接进行 d p dp dp转移的话显然会 T T T
但我们可以猜测到一点,当 K K K比较大的时候,我们应该很容易划分这个序列使得每个数在其区间中,与除自己外的所有数的 gcd ⁡ \gcd gcd都不大于改区间的 l l l
也就是说我的的答案就为 n n n
有了这个结论,我们就可以去想想怎么划分。
一个区间 [ l , r ] [l,r] [l,r]第一个会使得 gcd ⁡ ( l , r ) ⩾ l \gcd(l,r)\geqslant l gcd(l,r)l的数对显然是 ( l , 2 l ) (l,2l) (l,2l),也就是说,如果我们的 r < 2 l r< 2l r<2l,改区间答案便为 r − l + 1 r-l+1 rl+1
容易发现,这样的话,只要我们的 2 k > n 2^k>n 2k>n,就答案就为 n n n。也就是说,我们需要 d p dp dp K K K log ⁡ n \log n logn级别的,我们的总状态数是 n log ⁡ n n\log n nlogn级别。

我们的状态数大概无法继续优化了,但我们的 d p dp dp转移是可继续优化的。
我们用 c ( l , r ) c(l,r) c(l,r)表示将区间 [ l , r ] [l,r] [l,r]划分一段所产生的贡献,则转移式为:
d p i , k = min ⁡ j = 0 i − 1 ( d p j , k − 1 + c ( j + 1 , i ) ) dp_{i,k}=\min_{j=0}^{i-1}\left(dp_{j,k-1}+c(j+1,i)\right) dpi,k=j=0mini1(dpj,k1+c(j+1,i))显然,这个 d p dp dp转移是单调的,对于 k k k相同的 d p dp dp,显然我 i i i越大,我们的划分点就会继续右移。
事实上,我们可以通过说明对于 l < l ′ < r ′ < r l<l'<r'<r l<l<r<r,有 c ( l , r ) + c ( l ′ , r ′ ) ⩾ c ( l , r ′ ) + c ( l ′ , r ) c(l,r)+c(l',r')\geqslant c(l,r')+c(l',r) c(l,r)+c(l,r)c(l,r)+c(l,r)来证明它的单调性。
而这个式子显然是成立的,前面的部分比后面多了区间 [ l , l ′ ) [l,l') [l,l)与区间 ( r ′ , r ] (r',r] (r,r]间的贡献。
这样的话,我们就可以用分治优化我们的 d p dp dp转移了。

但无论怎么转移,我们都需要快速求出我们的 c ( l , r ) c(l,r) c(l,r)呀。
推一推 c c c的式子。
c ( l , r ) = ∑ i = l r ∑ j = i r [ ( i , j ) ⩾ l ] = ∑ d = l r ∑ i = l r ∑ j = i r [ ( i , j ) = d ] = ∑ d = l r ∑ i = 1 ⌊ r d ⌋ ∑ j = 1 i [ ( i , j ) = 1 ] = ∑ d = l r ∑ i = 1 ⌊ r d ⌋ φ ( i ) = ∑ d = l r s ( r d ) c(l,r)=\sum_{i=l}^{r}\sum_{j=i}^{r}[(i,j)\geqslant l]=\sum_{d=l}^{r}\sum_{i=l}^{r}\sum_{j=i}^{r}[(i,j)=d]\\ =\sum_{d=l}^{r}\sum_{i=1}^{\lfloor\frac{r}{d}\rfloor}\sum_{j=1}^{i}[(i,j)=1]=\sum_{d=l}^{r}\sum_{i=1}^{\lfloor\frac{r}{d}\rfloor}\varphi(i)=\sum_{d=l}^{r}s(\frac{r}{d}) c(l,r)=i=lrj=ir[(i,j)l]=d=lri=lrj=ir[(i,j)=d]=d=lri=1drj=1i[(i,j)=1]=d=lri=1drφ(i)=d=lrs(dr)其中 s ( n ) = ∑ i = 1 n φ ( i ) s(n)=\sum_{i=1}^n\varphi(i) s(n)=i=1nφ(i),也就是 φ \varphi φ的前缀和。
而这就可以整除分块了,我们可以先预处理一下块的前缀和,每次查询就算一下它所在块后缀和,再加上整块的后缀和就行了。
这样就可以做到预处理 O ( n n ) O\left(n\sqrt n\right) O(nn ),单次查询 O ( 1 ) O\left(1\right) O(1)
事实上我们可以再将整个 d p dp dp数组都预处理了,然后每次询问就可以 O ( 1 ) O\left(1\right) O(1)回答了。

总时间复杂度 O ( n log ⁡ 2 n + n n ) O\left(n\log^2 n+n\sqrt n\right) O(nlog2n+nn )

源码

#pragma GCC optimize(2)
#pragma GCC optimize(3)
#pragma GCC optimize("Ofast")
#include<bits/stdc++.h>
using namespace std;
#define MAXN 100005
#define lowbit(x) (x&-x)
#define reg register
#define pb push_back
#define mkpr make_pair
#define fir first
#define sec second
#define lson (rt<<1)
#define rson (rt<<1|1)
typedef long long LL;
typedef unsigned long long uLL; 
typedef long double Ld;
typedef pair<LL,LL> pii;
const LL INF=0x3f3f3f3f3f3f3f3f;
const int mo=998244353;
const int mod=1e6+7;
const int inv2=499122177;
const int inv3=332748118;
const int jzm=2333;
const int zero=2000;
const int n1=100;
const int lim=100000;
const int orG=3,ivG=332748118;
const double Pi=acos(-1.0);
const double feps=1e-11;
const double eps=1e-9;
template<typename _T>
_T Fabs(_T x){return x<0?-x:x;}
template<typename _T>
void read(_T &x){
	_T f=1;x=0;char s=getchar();
	while(s>'9'||s<'0'){if(s=='-')f=-1;s=getchar();}
	while('0'<=s&&s<='9'){x=(x<<3)+(x<<1)+(s^48);s=getchar();}
	x*=f;
}
template<typename _T>
void print(_T x){if(x<0)putchar('-'),print(-x);if(x>9)print(x/10);putchar(x%10+'0');}
LL gcd(LL a,LL b){return !b?a:gcd(b,a%b);}
int add(int x,int y,int p){return x+y<p?x+y:x+y-p;}
void Add(int &x,int y,int p){x=add(x,y,p);}
int qkpow(int a,int s,int p){int t=1;while(s){if(s&1)t=1ll*t*a%p;a=1ll*a*a%p;s>>=1;}return t;}
int T,n,K,prime[MAXN],cntp,phi[MAXN];
LL pre[MAXN],dp[MAXN][20],mp1[MAXN][405],mp2[MAXN][405];
int al[MAXN],ar[MAXN],idx;
bool oula[MAXN];
void init(){
	pre[1]=1;
	for(int i=2;i<=n;i++){
		if(!oula[i])prime[++cntp]=i,phi[i]=i-1;
		for(int j=1;j<=cntp&&i*prime[j]<=n;j++){
			oula[i*prime[j]]=1;
			if(i%prime[j]==0){phi[i*prime[j]]=prime[j]*phi[i];continue;}
			else phi[i*prime[j]]=(prime[j]-1)*phi[i];
		}
		pre[i]=pre[i-1]+phi[i];
	}
	for(int i=1;i<=n;i++){
		idx=0;LL summ=0;
		for(int l=1,r;l<=i;l=r+1)
			r=i/(i/l),idx++,al[idx]=l,ar[idx]=r;
		for(int j=idx;j>0;j--){
			summ+=1ll*(ar[j]-al[j]+1)*pre[i/ar[j]];
			if(al[j]<=i/al[j])mp1[i][al[j]]=summ;
			else mp2[i][i/al[j]]=summ;
		}
	}
}
LL work(int L,int R){
	if(L>R)return 0;int r=R/(R/L)+1;
	return 1ll*(r-L)*pre[R/L]+(r<=R/r?mp1[R][r]:mp2[R][R/r]);
}
void sakura(int l,int r,int L,int R,int k){
	int mid=l+r>>1;dp[mid][k]=INF;
	LL tmp=work(R+1,mid);int mk=0;
	for(int i=min(mid,R);i>=L;i--){
		if(dp[i][k-1]+tmp<dp[mid][k])
			dp[mid][k]=dp[i][k-1]+tmp,mk=i;
		if(i&&i<=mid)tmp+=pre[mid/i];
	}
	if(l<mid)sakura(l,mid-1,L,mk,k);
	if(r>mid)sakura(mid+1,r,mk,R,k);
}
int main(){
	read(T);n=100000;K=16;init();
	dp[0][0]=0;for(int i=1;i<=n;i++)dp[i][0]=INF;
	for(int i=1;i<=K;i++)sakura(0,n,0,n,i);
	while(T--){
		read(n);read(K);
		if(K>16||(1<<K)-1>=n){printf("%d\n",n);continue;}
		printf("%lld\n",dp[n][K]);
	}
	return 0;
}

谢谢!!!

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值