#分治,决策单调性dp#CF868F Yet Another Minimization Problem

题目

给定一个序列 (a),要把它分成 (k) 个子段。((nleq 10^5,kleq 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]);
}
原文地址:https://www.cnblogs.com/Spare-No-Effort/p/14979937.html