#分治,决策单调性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]);
}
posted @ 2021-07-07 09:06  lemondinosaur  阅读(49)  评论(0)    收藏  举报