CF833B The Bakery

本文详细解析了TheBakery题目中的动态规划算法,并结合线段树进行优化,避免了O(n^2k)的时间复杂度。通过预处理颜色位置、使用线段树维护区间最值,实现了高效的dp状态转移。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目:The Bakery

kevin_yu的题解

之前没有好好听@kevin_yu讲题,做了一晚上……

这篇题解主要是对kevin_yu的补充以及代码注释的扩充。

思路:

part 1.dp

一个很容易想到的 O ( n 2 k ) O(n^2k) O(n2k)算法——
f [ i ] [ j ] f[i][j] f[i][j]表示前i的位置划分j次的价值。
转移方程:
f [ i ] [ j ] = m a x   ( f [ k ] [ j − 1 ] + c n t [ k ] [ i ] ) f[i][j]=max \ (f[k][j-1]+cnt[k][i] ) f[i][j]=max (f[k][j1]+cnt[k][i])
其中,k是一个中间值,代表划分枚举的位置。

然后,我们知道这样做一定会超时,所以我们需要优化。


part 2.线段树

我们选择优化转移,也就是说省去中间值k的枚举。
再看我们的转移方程,可以发现,我们取得是 f [ k ] [ j − 1 ] + c n t [ k ] [ i ] f[k][j-1]+cnt[k][i] f[k][j1]+cnt[k][i]这一段的最大值。
对于这种类型的取max的dp的优化,可以选择 单调队列优化 / nlogn数据结构优化。
如果使用单调队列,我们可以处理出   m a x   f [ k ] [ j − 1 ] \ max\ {f[k][j-1]}  max f[k][j1],但是 c n t [ k ] [ i ] cnt[k][i] cnt[k][i]这种数据却无法操作。
所以考虑数据结构,也就是动态区间最值的最简单工具——线段树。

令pre[i]表示上一次出现与i同颜色的蛋糕的位置。
假设我们在pre[i]和i之间取分割点k,那么末点在区间 [ k , i ] [k,i] [k,i]间都是可以拥有col[i]的,而 [   p r e [ i ] , k ) [\ pre[i] , k) [ pre[i],k)这一段一定是没有col[i]的。

这样看,我们每次在线段树上更新 [ p r e [ i ] , i ] [pre[i],i] [pre[i],i],查询 [ 1 , i ] [1,i] [1,i]就可以处理cnt的问题了。
而f[k][j-1]只需要在建树时加上就好。

具体实现见代码及注释——


代码:

#include<bits/stdc++.h>
using namespace std;

#define maxn 35000
#define maxm 50
#define read(x) scanf("%d",&x)

#define lson (o*2)
#define rson (o*2+1)
#define mid (L+(R-L)/2)

int n,m;	
int f[maxn+5][maxm+5];	//f[i][j]:前i的位置划分j次的价值 

int pre[maxn+5];	//上一次出现与i同颜色的蛋糕的位置
map<int,int> mp;	//用来更新pre 

//线段树部分 

int a[maxn*10+5];	//线段树 
int lzy[maxn*10+5];	//lazy tag

void push_up(int o) {	//通过子节点更新父节点的值 
	a[o]=max(a[lson],a[rson]);
}

void make_tree(int o,int L,int R,int row) {	//建树
	a[o]=lzy[o]=0; 
	if(L==R) {
		a[o]=f[L-1][row-1];
		return ;
	}
	make_tree(lson,L,mid,row),make_tree(rson,mid+1,R,row);
	push_up(o);
}

void push_down(int o) {	//下传lazy tag 
	a[lson]+=lzy[o],a[rson]+=lzy[o];
	lzy[lson]+=lzy[o],lzy[rson]+=lzy[o];
	lzy[o]=0;
}

int P,Q;	//修改及查询的区间 

void update(int o,int L,int R) {	//更新 
	if(L>Q||R<P) return ;
	if(L>=P&&R<=Q) {
		lzy[o]++;
		a[o]++;
		return ;
	}
	push_down(o);
	update(lson,L,mid),update(rson,mid+1,R);
	push_up(o);
}

int query(int o,int L,int R) {	//查询 
	if(L>Q||R<P) return 0;
	if(L>=P&&R<=Q) {
		return a[o];
	}
	push_down(o);
	return max(query(lson,L,mid),query(rson,mid+1,R));
}

int main() {
	read(n),read(m);
	for(int i=1;i<=n;i++) {
		int x;
		read(x);
		if(mp.count(x)) pre[i]=mp[x]+1;
		else pre[i]=1;
		mp[x]=i;
	}
	for(int j=1;j<=m;j++) {
		make_tree(1,1,n,j);
		for(int i=1;i<=n;i++) {
			P=pre[i],Q=i;
			update(1,1,n);
			P=1,Q=i;
			f[i][j]=query(1,1,n);
		}
	}
	
	printf("%d",f[n][m]);
	
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值