「NOI2009」二叉查找树 区间DP 记忆化搜索

「NOI2009」二叉查找树

区间DP 记忆化搜索

Part 0

有几个性质:

1.由于本题是按照数据值排序的二叉查找树,那么这颗树的中序遍历是确定的(按照数据值排序后的序列)。故对于一个节点区间,如果我们确定了他的根是什么,就可以确定他的左右子树分别有哪些点。不难发现,这两个子树之间的贡献是没有关联的。故可以以此一步步缩小问题规模。

举个例子,先得到中序遍历,对于区间[L,R],如果以i为根,那么它的左子树就是[L,i-1],右子树就是[i+1,R].

2.因为一个点的权值可以修改成任意实数。故如果我们修改了一个点的权值,那么在保证其他没修改权值的点之间满足条件时,一定存在一个权值使这个点也满足条件。

Part 1

先从暴力开始。

设计状态:

对于一个子树,它的节点在总的中序遍历中对应的是一段区间。故我们确定这么一颗子树要知道它对应区间的左右端点。

由于每个结点的权值都比它的儿子结点的权值要小,故我们可以约束子树中的最小权值。然后我们还要知道这个子树的根的深度。

然后我们就可以定义状态 d p [ L ] [ R ] [ l i m ] [ d e p ] dp[L][R][lim][dep] dp[L][R][lim][dep] [ L , R ] [L,R] [L,R]区间内的节点构成的,其根结点深度为dep,所有没有修改的点的权值都大于(大于等于也可以)lim的一颗子树对于答案的最小贡献。

这里由于一开始的权值比较大,故可以先离散化处理。

转移:

我们先枚举这个根结点,如果这个根结点。如果这个根结点的权值已经小于等于lim,那么这个根结点必须修改权值,否则可以不改。然后就可以得出转移方程。

val[i]为i点的权值。cnt[i]为i点的访问频度。

修改:

d p [ L ] [ R ] [ l i m ] [ d e p ] = min ⁡ ( c n t [ R o o t ] ∗ d e p + K + d p [ L ] [ R o o t − 1 ] [ l i m ] [ d e p + 1 ] + d p [ R o o t + 1 ] [ R ] [ l i m ] [ d e p + 1 ] ) dp[L][R][lim][dep]=\min(cnt[Root]*dep+K+dp[L][Root-1][lim][dep+1]+dp[Root+1][R][lim][dep+1]) dp[L][R][lim][dep]=min(cnt[Root]dep+K+dp[L][Root1][lim][dep+1]+dp[Root+1][R][lim][dep+1])

不修改:首先得满足 v a l [ R o o t ] > l i m val[Root]>lim val[Root]>lim

d p [ L ] [ R ] [ l i m ] [ d e p ] = min ⁡ ( c n t [ R o o t ] ∗ d e p + d p [ L ] [ R o o t − 1 ] [ v a l [ R o o t ] ] [ d e p + 1 ] + d p [ R o o t + 1 ] [ R ] [ v a l [ R o o t ] ] [ d e p + 1 ] ) dp[L][R][lim][dep]=\min(cnt[Root]*dep+dp[L][Root-1][val[Root]][dep+1]+dp[Root+1][R][val[Root]][dep+1]) dp[L][R][lim][dep]=min(cnt[Root]dep+dp[L][Root1][val[Root]][dep+1]+dp[Root+1][R][val[Root]][dep+1])

这个的复杂度是 O ( n 5 ) O(n^5) O(n5)(一共有 n 4 n^4 n4种状态,转移复杂度 O ( n ) O(n) O(n))

Part 2

然后我们发现dep这一维可以省(可能有些人早发现了,反正我是敲完之后才发现的。),因为我们将两个子树的状态通过枚举的Root合并时就是将两个子树内每个节点的深度都加1,所以加上他们的访问深度之和即可,因为还要加上根结点的访问次数,故就是加上 s u m [ L , R ] sum[L,R] sum[L,R]

此时的状态定义就是 d p [ L ] [ R ] [ l i m ] dp[L][R][lim] dp[L][R][lim]表示子树的节点区间在[L,R],所有为修改的点的权值都大于lim的一颗子树的最小贡献。(也可以理解成只有这一颗子树的最小答案,与上面不同的是这不是对全局答案的最小贡献)。

转移方程就变成了

(修改) d p [ L ] [ R ] [ l i m ] = min ⁡ ( c n t [ R o o t ] ∗ d e p + K + d p [ L ] [ R o o t − 1 ] [ l i m ] + d p [ R o o t + 1 ] [ R ] [ l i m ] + s u m [ R ] − s u m [ L − 1 ] ) dp[L][R][lim]=\min(cnt[Root]*dep+K+dp[L][Root-1][lim]+dp[Root+1][R][lim]+sum[R]-sum[L-1]) dp[L][R][lim]=min(cnt[Root]dep+K+dp[L][Root1][lim]+dp[Root+1][R][lim]+sum[R]sum[L1])

不修改类似,这里就不复述了。

复杂度就只有 O ( n 4 ) O(n^4) O(n4)

AC代码:

(这里由于切分的代码与正解极其类似,限于篇幅就不给出了)

#include<cstdio>
#include<algorithm>
#include<cstring>
#define M 75
using namespace std;
struct node{
	int Data,val,cnt;
	bool operator <(const node &_)const{
		return Data<_.Data;
	}
}A[M];
int n,K;
void check_min(long long &x,long long y){if(x>y)x=y;}
long long dp[M][M][M];
int B[M];
int sum[M];
long long dfs(int L,int R,int lim){
	long long &res=dp[L][R][lim];
	if(L>R){res=0;return 0;}
	if(~res)return res;
	res=1e18;
	if(L==R){//分治到了底层 
		res=0;
		if(A[L].val<=lim)res=K;
		res+=1ll*A[L].cnt;
		return res;
	}
	for(int Root=L;Root<=R;Root++){
		long long tmp;
		int add=sum[R]-sum[L-1];//合并后增加的 
		tmp=K+dfs(L,Root-1,lim)+dfs(Root+1,R,lim)+add;//修改当前点的权值 
		check_min(res,tmp);
		if(A[Root].val<=lim)continue;
		tmp=dfs(L,Root-1,A[Root].val)+dfs(Root+1,R,A[Root].val)+add;//不修改当前点的权值 
		check_min(res,tmp);
	}
	return res;
}
void Solve(){
	memset(dp,-1,sizeof(dp));
	for(int i=1;i<=n;i++)B[i]=A[i].val;
	sort(B+1,B+n+1);
	int sz=unique(B+1,B+n+1)-B-1;
	for(int i=1;i<=n;i++)A[i].val=(lower_bound(B+1,B+sz+1,A[i].val)-B);//离散化权值 
	sort(A+1,A+n+1);//求出中序遍历 
	sum[0]=0;for(int i=1;i<=n;i++)sum[i]=sum[i-1]+A[i].cnt;
	printf("%lld\n",dfs(1,n,0));
}
int main(){
	scanf("%d%d",&n,&K);
	for(int i=1;i<=n;i++)scanf("%d",&A[i].Data);
	for(int i=1;i<=n;i++)scanf("%d",&A[i].val);
	for(int i=1;i<=n;i++)scanf("%d",&A[i].cnt);
	Solve();
	return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值