二叉搜索树TREE(线段树,区间DP)

57 篇文章 0 订阅
53 篇文章 0 订阅

前言

线段树+区间DP题,线段树却不是优化DP的,是不是很意外?

题面

二叉搜索树是一种二叉树,每个节点都有一个权值,并且一个点的权值比其左子树里的点权值都大,比起右子树里的点权值都小。

一种朴素的向二叉搜索树中插入节点的算法是,将新节点作为一个新的叶子节点插入树中,维持二叉搜索树的性质,并且不移动原有的节点。

现在有一个长度为 n n n 的排列 a a a,你可以任意重排第 l l l 到第 r r r 个数,但不移动其余数。接下来依次从 1 1 1 n n n a i a_i ai 作为新节点的权值插入一棵本来为空的二叉搜索树。试问得到的二叉搜索树所有节点的深度和最小是多少?(假定根节点的深度为 1 1 1)。

输入

第一行一个数 n n n ,( 1 ≤ n ≤ 1 0 5 1\leq n\leq 10^5 1n105)。
接下来一行一个排列 a a a
最后一行两个数 l l l, r r r,( 1 ≤ r − l + 1 ≤ 200 1\leq r-l+1\leq 200 1rl+1200)。

输出

一行一个答案。

题解

所有节点的深度和,等于所有子树大小之和。这样想 l ∼ r l\sim r lr 以内的变动就不会影响 r r r 后面的点了。

我们再分析往二叉搜索树中添加节点的过程,新点要成为叶子,并且在原数列的正确位置。假设当前数 x x x 的前一个数为 L L L ,后一个为 R R R ,我们会发现 L + 1 ∼ R − 1 L+1\sim R-1 L+1R1 以内的数都一定在 x x x 的子树中了,而且只有这些数在 x x x 的子树中。后者很好理解,前者是因为,该范围内除了 x x x 以外的数都比 x x x 晚成为叶子,因此若成为了 L L L R R R 的后代便会矛盾。

所以,加点操作可以转化为:在一个数轴对应位置上加一个点,记前一个点位置为 L L L ,后一个点位置为 R R R ,对答案贡献 R − L − 1 R-L-1 RL1 ,维护前后位置是经典的线段树操作了,当然也可以直接用 set ,快捷得多。

我们考虑 1 ∼ R 1\sim R 1R 加完后的序列,如果 L ∼ R L\sim R LR 的点此时两两不相邻,那我们就没得变了,答案都是确定的了,因为每个点的贡献都取决于 1 ∼ L − 1 1\sim L-1 1L1 的点了。但是,如果存在相邻的一段( L ∼ R L\sim R LR 的点),我们就有的选了,可以通过安排这些点的加入顺序来改变贡献。假设有相邻的一段 m m m 个点,分割出 m + 1 m+1 m+1 个线段,我们的操作就是每次把相邻的两条线段合并成一条线段,并算上这条线段的长度作为贡献,直到最后只有一条线段。

是不是很像合并果子?但是不一样,我们只能对相邻的果子进行操作。于是只能用区间 DP,这就是数据范围 r − l + 1 ≤ 200 r-l+1\leq200 rl+1200 的作用,时间复杂度 O ( l e n g t h 3 ) O({\rm length}^3) O(length3) 能过。我们需要对形成的每段连续点分别进行区间 DP 。

总时间复杂度 O ( n log ⁡ n + ( r − l + 1 ) 3 ) O(n\log n+(r-l+1)^3) O(nlogn+(rl+1)3)

CODE

#include<set>
#include<map>
#include<stack>
#include<cmath>
#include<ctime>
#include<queue>
#include<bitset>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define MAXN 100005
#define LL long long
#define DB double
#define ENDL putchar('\n')
#define lowbit(x) (-(x) & (x))
#define FI first
#define SE second
#define SI(x) multiset<x>::iterator
#define MI map<int,int>::iterator
#define eps (1e-4)
LL read() {
	LL f=1,x=0;char s = getchar();
	while(s < '0' || s > '9') {if(s=='-')f = -f;s = getchar();}
	while(s >= '0' && s <= '9') {x=x*10+(s-'0');s = getchar();}
	return f*x;
}
void putpos(LL x) {
	if(!x) return ;
	putpos(x/10); putchar('0'+(x%10));
}
void putnum(LL x) {
	if(!x) putchar('0');
	else if(x < 0) putchar('-'),putpos(-x);
	else putpos(x);
}
void AIput(LL x,char c) {putnum(x);putchar(c);}

int n,m,s,o,k;
int L,R;
struct it{
	int l,r;it(){l=0x3f3f3f3f,r=-1;}
	it(int L,int R){l=L;r=R;}
}tre[MAXN<<2];
it merg(it a,it b) {return it(min(a.l,b.l),max(a.r,b.r));}
int M;
void maketree(int n) {
	M=1;while(M<n+2) M<<=1;
	for(int i = M+1;i <= M+n;i ++) tre[i] = it(n+1,0);
	for(int i = M-1;i > 0;i --) tre[i] = merg(tre[i<<1],tre[i<<1|1]);
}
void addtree(int x,it y) {
	int s = M+x;tre[s] = y;s >>= 1;
	while(s) tre[s] = merg(tre[s<<1],tre[s<<1|1]),s >>= 1;
}
it findtree(int l,int r) {
	if(l > r) return it(r+1,l-1);
	it as = it();
	int s = M+l-1,t = M+r+1;
	while(s || t) {
		if((s>>1) != (t>>1)) {
			if(!(s&1)) as = merg(as,tre[s^1]);
			if(t & 1) as = merg(as,tre[t^1]);
		}else break;
		s >>= 1;t >>= 1;
	}return as;
}
int findl(int x) {
	it as = findtree(1,x-1); return as.r;
}
int findr(int x) {
	it as = findtree(x+1,n); return as.l;
}
int a[MAXN];
LL dp[205][205],sum[205];
LL DP(int *s,int n) {
	if(n < 1) return 0ll;
	sum[0] = 0ll;
	for(int i = 1;i <= n;i ++) {
		sum[i] = sum[i-1] + s[i];
		dp[i][i] = 0;
	}
	for(int i = 2;i <= n;i ++) {
		for(int j = i-1;j > 0;j --) {
			dp[j][i] = 1e18;
			for(int k = j;k < i;k ++) {
				dp[j][i] = min(dp[j][i],dp[j][k] + dp[k+1][i]);
			}
			dp[j][i] += sum[i] - sum[j-1] + i-j;
		}
	}
	return dp[1][n];
}
int main() {
	n = read();
	for(int i = 1;i <= n;i ++) {
		a[i] = read();
	}
	maketree(n);
	L = read();R = read();
	LL ans = 0;
	for(int i = 1;i <= L-1;i ++) {
		addtree(a[i],it(a[i],a[i]));
		int ll = findl(a[i]),rr = findr(a[i]);
		ans += rr-ll-1;
	}
	sort(a + L,a + R + 1);
	for(int i = L;i <= R;i ++) addtree(a[i],it(a[i],a[i]));
	int tm[205] = {},cntm = 0;
	for(int i = L;i <= R;i ++) {
		int j = i;
		cntm = 0;
		tm[++ cntm] = a[i] - findl(a[i]) - 1;
		while(j < R && findl(a[j+1]) == a[j]) {
			j ++; tm[++ cntm] = a[j] - findl(a[j]) - 1;
		}
		tm[++ cntm] = findr(a[j]) - a[j] - 1;
		ans += DP(tm,cntm);
		i = j;
	}
	for(int i = R+1;i <= n;i ++) {
		addtree(a[i],it(a[i],a[i]));
		int ll = findl(a[i]),rr = findr(a[i]);
		ans += rr-ll-1;
	}
	printf("%lld\n",ans);
	return 0;
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值