CF833B 题解

发布时间 2023-08-11 16:17:40作者: MrcFrst_LRY

原题传送门

题意:将一个长度为 \(n\) 的序列划分成连续的 \(k\) 段,每一段的价值为段内不同的数字的数量,求最大价值。\((n\leq35000,k\leq50)\)

划分问题,可以考虑 \(dp\) 。设 \(dp_{i,j}\) 为将前 \(i\) 个数划分成 \(j\) 段可得到的最大价值,\(w_{l,r}\)为区间\([l,r]\)的价值。方程很容易写出来。

\[dp_{i,j}=\max_{k=1}^{i-1}{dp_{k,j-1}+w_{{k+1},i}} \]

暴力转移的时间复杂度为\(O({n^2}k)\),显然寄掉,考虑优化\(:\)

首先此题的要点显然就在于题目中价值的定义。显然它满足区间包含单调性,然后再简单的手玩一下可以发现它也满足四边形不等式(如果我没出错的话应该是四边形恒等式,但是反正不影响就是了)

既然 \(w\) 满足四边形不等式区间包含单调性,那么\(dp\)当然就满足四边形不等式。进一步说明,\(dp\)具有决策单调性。此题的重头戏就在这里。

那就直接二分呗,用分治就好了,至于为什么,我觉得就是个经典\(Trick\),甚至好像不太像是\(Trick?\),总之学了就知道了,详见OI Wiki

分治的过程比较板,不多说了,然后再来考虑如何计算 \(w\) ,显然双指针就好了,就和莫队里面的一样。

然后就结束了。时间复杂度:\(O(kn\log n)\)

\(Code:\)

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define il inline
#define re register
const int N=35100,K=60;
int n,k,a[N],res,cnt[N],dp[N][K],L,R;
il void init(){
	for(re int i=0;i<=n;i++)for(re int j=0;j<=k;j++)dp[i][j]=-1;
	dp[0][0]=0;
}
il void add(int x){
	if(!cnt[x])res++;
	cnt[x]++;
}
il void del(int x){
	cnt[x]--;
	if(!cnt[x])res--;
}
il int query(int l,int r){
	while(L>l)add(a[--L]);
	while(R<r)add(a[++R]);
	while(L<l)del(a[L++]);
	while(R>r)del(a[R--]);
	return res;
}
il void solve(int l,int r,int optl,int optr,int id){
	if(l>r)return;
	int mid=(l+r)>>1,opt;
	for(re int i=optl;i<=min(mid,optr);i++){
		int tmp=dp[i-1][id-1]+query(i,mid);
		if(tmp>dp[mid][id])dp[mid][id]=tmp,opt=i;
	}
	solve(l,mid-1,optl,opt,id);
	solve(mid+1,r,opt,optr,id);
}
il int read(){
    re int x=0,f=1;char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
    while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+(c^48),c=getchar();
    return x*f;
}
int main(){
	n=read(),k=read();
	for(re int i=1;i<=n;i++)a[i]=read();
	init();
	for(re int i=1;i<=k;i++)solve(1,n,1,n,i);
	cout<<dp[n][k];
    return 0;
}