#分治,决策单调性dp#CF868F Yet Another Minimization Problem
题目
给定一个序列 \(a\),要把它分成 \(k\) 个子段。(\(n\leq 10^5,k\leq 20\))
每个子段的费用是其中相同元素的对数。求所有子段的费用之和的最小值。
分析
有一个很明显的\(dp\)就是设\(dp[i][k]\)表示前\(i\)个数分成\(k\)段的费用之和最小值,
那么\(dp[i][k]=\min\{dp[j][k-1]+calc(j+1,i)\}\)
可以发现\(dp[i][k]\)不会因为\(dp[i'][k]\)而改变,用分治解决
并且无法在\(O(1)\)时间内求出来,并且应该是具有决策单调性的,
考虑calc函数用类似于莫队的方法求解,那么就可以做到\(O(knlog_2n)\)
代码
#include <cstdio>
#include <cctype>
#include <cstring>
#define rr register
using namespace std;
typedef long long lll;
const int N=100011;
lll dp[N],f[N],now;
int a[N],cnt[N],n,m,Le=1,Ri;
inline signed iut(){
rr int ans=0; rr char c=getchar();
while (!isdigit(c)) c=getchar();
while (isdigit(c)) ans=(ans<<3)+(ans<<1)+(c^48),c=getchar();
return ans;
}
inline lll answ(int l,int r){
while (Le<l) now-=--cnt[a[Le++]];
while (Le>l) now+=cnt[a[--Le]]++;
while (Ri<r) now+=cnt[a[++Ri]]++;
while (Ri>r) now-=--cnt[a[Ri--]];
return now;
}
inline void dfs(int l,int r,int L,int R){
if (l>r) return;
rr int mid=(l+r)>>1,MID=L;
rr lll ans=1ll<<60;
for (rr int j=L;j<mid&&j<=R;++j){
rr lll t=answ(j+1,mid);
if (f[j]+t<ans) ans=f[j]+t,MID=j;
}
dp[mid]=ans;
dfs(l,mid-1,L,MID),dfs(mid+1,r,MID,R);
}
signed main(){
n=iut(); m=iut();
for (rr int i=1;i<=n;++i) a[i]=iut();
for (rr int i=1;i<=n;++i) dp[i]=answ(1,i);
for (rr int i=2;i<=m;++i)
memcpy(f,dp,sizeof(dp)),dfs(1,n,1,n);
return !printf("%lld",dp[n]);
}

浙公网安备 33010602011771号