题目
一个长为n(n<=1e5)的数组a[],ai<=n,
要求把这个数组分成k个连续的段,
每一段的代价,是其中相同的值产生的无序对数量之和
最小化代价,求这个代价
思路来源
官方题解
题解 CF868F 【Yet Another Minimization Problem】 - zhongyuwei 的博客 - 洛谷博客
题解
先按官方题解,证一下决策点的单调性,
即设[1,i]分成k段时,最后一段为[pos,i],
则[1,i+1]分成k段时,最后一段为[pos2,i],一定满足pos2>=pos,
证明可参考题解,反证法设pos2<pos,
有一个cost(x,j2)-cost(p(j1),j2)>=cost(x,j1)-cost(p(j1),j1)关键式子,
可以移项cost(x,j2)-cost(x,j1)>=cost(p(j1),j2)-cost(p(j1),j1),x<p(j1),
感性理解一下就是,由于x<p(j1),所以动态插入[j1+1,j2]时,
每个值在长区间的贡献都大于等于在短区间的贡献,显然成立,
根据该式可推得矛盾,即得原决策点的单调性
方法论是,如果求出dp[k][mid]的决策点pos1,
则dp[k][l到mid-1]的决策点pos0<=pos1,dp[k][mid+1到r]的决策点pos2>=pos1,
然后,分治这个决策的过程,每次找出区间中点mid的最优值和决策点,递归即可
区间段是暴力挪两个端点nl和nr到与[l,r]对齐的,
可以参考思路来源,证明这个挪动过程也是O(nlogn)
所以,总复杂度O(knlogn)
代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10,M=21,INF=0x3f3f3f3f3f3f3f3fll;
typedef long long ll;
int n,k,a[N],cnt[N],nl,nr;
//dp[i][j]表示i段只考虑[1,j]时的最小代价和
ll dp[M][N],now;
ll cal(int l,int r){
while(nl<l)now-=(cnt[a[nl]]-1),cnt[a[nl]]--,nl++;
while(nl>l)nl--,now+=cnt[a[nl]],cnt[a[nl]]++;
while(nr<r)nr++,now+=cnt[a[nr]],cnt[a[nr]]++;
while(nr>r)now-=(cnt[a[nr]]-1),cnt[a[nr]]--,nr--;
return now;
}
//要解决dp[l][k]到dp[r][k]的问题 当前最优决策点[pl,pr] 先解决dp[mid][k] 并确定mid的决策点
void cdq(int x,int l,int r,int pl,int pr){
int mid=(l+r)/2,up=min(mid,pr),pos=1;
for(int i=pl;i<=up;++i){
ll v=cal(i,mid);
if(dp[x][mid]>dp[x-1][i-1]+v){
dp[x][mid]=dp[x-1][i-1]+v;
pos=i;
}
}
if(l<=mid-1)cdq(x,l,mid-1,pl,pos);
if(mid+1<=r)cdq(x,mid+1,r,pos,pr);
}
int main(){
memset(dp,INF,sizeof dp);
scanf("%d%d",&n,&k);
for(int i=1;i<=n;++i){
scanf("%d",&a[i]);
}
dp[1][0]=0;
for(int i=1;i<=n;++i){
dp[1][i]=dp[1][i-1]+cnt[a[i]];
cnt[a[i]]++;
}
nl=1;nr=n;now=dp[1][n];
for(int i=2;i<=k;++i){
dp[i][0]=0;
cdq(i,1,n,1,n);
}
printf("%lld\n",dp[k][n]);
return 0;
}