CF833B 题解
原题传送门
题意:将一个长度为 \(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;
}