Codeforces Round #438 F.Yet Another Minimization Problem(决策单调性 分治优化dp)

题目

一个长为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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Code92007

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值