CF868F Yet Another Minimization Problem

CF868F Yet Another Minimization Problem

题目大意比较清楚,这里就不扯了

最朴素的做法是直接 O ( n 2 k )     D P O(n^2k) \ \ \ DP O(n2k)   DP
发现会T上天
然后就有了决策单调性优化
意思就是设 d p [ i ] [ j ] 表 示 前 i 个 数 , 分 成 j 段 的 最 小 值 是 多 少 dp[i][j] 表示前i个数,分成j段的最小值是多少 dp[i][j]ij
然后如果 d p [ i ] [ j ] 是 由 d p [ k ] [ j − 1 ] dp[i][j] 是由 dp[k][j-1] dp[i][j]dp[k][j1]转移过来的,那么 d p [ i − 1 ] [ j ] 一 定 是 从 d p [ l ] [ j − 1 ] 转 移 过 来 的 ( l &lt; = k ) dp[i-1][j]一定是从dp[l][j-1]转移过来的(l&lt;=k) dp[i1][j]dp[l][j1](l<=k)
具体证明要推推式子,这里就省略了
然后怎么做呢?
直接维护肯定是比较麻烦的(涉及到每个数出现的个数)
可以考虑先把dp数组分成k层
然后发现每次都是从上一层的转移过来的
然后再设
c a l c ( l , r , l l , r r ) 表 示 d p [ l . . . . . r ] [ k ] 的 d p 值 是 由 d p [ l l . . . . . . . r r ] [ k − 1 ] 转 移 过 来 的 calc(l, r, ll, rr)表示dp[l.....r][k]的dp值是由dp[ll.......rr][k-1]转移过来的 calc(l,r,ll,rr)dp[l.....r][k]dpdp[ll.......rr][k1]
然后就发现这个东西是可以二分(分治)的
令 m i d = ( l + r ) / 2 令mid = (l + r) / 2 mid=(l+r)/2
然后找到 d p [ m i d ] [ k ] dp[mid][k] dp[mid][k]的最优决策点p
然后再分治处理 c a l c ( l , m i d − 1 , l l , p ) 和 c a l c ( m i d + 1 , r , p , r r ) calc(l, mid - 1, ll, p) 和 calc(mid + 1, r, p, rr) calc(l,mid1,ll,p)calc(mid+1,r,p,rr)
然后就行了
关于费用的计算就直接像莫队一样移动一下L,R就行了(反正一共只会移动 n l o g n nlogn nlogn次)
代码可能好懂一些
代码中的dp数组的两维是反的
code:

// luogu-judger-enable-o2
#include<bits/stdc++.h>
#define N 1000005
#define int long long
using namespace std;
int L = 1, R, f[23][N], a[N], cnt[N], n, m, ans, cur;
void get(int l, int r){//动态算一下贡献
	while(L < l) cnt[a[L]] --, ans -= cnt[a[L]], L ++;
	while(R > r) cnt[a[R]] --, ans -= cnt[a[R]], R --;
	while(L > l) L --, ans += cnt[a[L]], cnt[a[L]] ++;
	while(R < r) R ++, ans += cnt[a[R]], cnt[a[R]] ++;
}
void calc(int l, int r, int ll, int rr){//如上面说的
	if(l > r) return;
	int p = 0, mid = (l + r) >> 1;
	for(int i = ll; i <= min(rr, mid - 1); i ++){//找dp[cur][mid]的最优决策点
		get(i + 1, mid);
		if(f[cur - 1][i] + ans < f[cur][mid]) 
			f[cur][mid] = f[cur - 1][i] + ans, p = i;
	}
	calc(l, mid - 1, ll, p);//分治处理
	calc(mid + 1, r, p, rr);
}
signed main(){
	memset(f, 0x3f, sizeof f);
	scanf("%lld%lld", &n, &m);
	for(int i = 1; i <= n; i ++) scanf("%lld", &a[i]);
	for(int i = 1; i <= n; i ++) get(1, i), f[1][i] = ans;//初始化
	for(int i = 2; i <= m; i ++) cur = i, calc(1, n, 0, n - 1);//一层层计算
	printf("%lld", f[m][n]);//输出
	return 0;
}
posted @ 2019-09-07 21:08  lahlah  阅读(19)  评论(0编辑  收藏  举报