[Gym 102798K] Tree Tweaking

一、题目

点此看题

给出一个 \(n\) 个点的排列 \(p\),现在要把这 \(n\) 个点按顺序建立二叉查找树,问把 \([L,R]\) 这段区间重排之后所得搜索树的最小深度和是多少。

\(n\leq 10^5,R-L< 200\)

二、解法

首先要知道如何建树,虽然我们不知道二叉搜索树怎么建但是知道 \(\tt Treap\) 怎么建,考虑二叉搜索树其实就是第 \(i\) 个点的 \(heap[i]=i\)\(\tt Treap\),我们考虑依据这个性质构建。

先把所有点按照 \((p_i,i)\) 放在坐标系上,然后发现要满足堆性质就要选纵坐标最小的点当根,然后向两边连左右儿子,横坐标自然满足二叉查找树的性质,发现这就是笛卡尔树,直接拿栈构建即可。

下面是 \(p=\{3,1,2,5,4\}\) 利用上述方法建出的树:

然后考虑重排 \([L,R]\) 其实就是任意交换 \(y\in[L,R]\) 这些点的 \(x\) 坐标,我们把这些点染色,考虑每个极大染色连通块内都只改变内部的结构,而且不会相互影响,其他连通块的结构都不变。原因很简单,因为重排 \([L,R]\) 并不改变其他点和染色点的偏序关系

那么考虑规划重排过程,对每个极大连通块分别考虑,因为子树大小和等于深度和,那么我们最小化染色点的子树大小和即可。设连通块大小为 \(m\),我们可以把这个连通块所接的 \(m+1\) 个子树全部取出来,把它们的 \(siz\) 按中序遍历排列成 \(a\),每次拿一个根然后把区间划分成两部分,这是显然的区间 \(dp\),设 \(f(l,r)\) 为规划区间 \([l,r]\) 所得的最小子树和:

\[f(l,r)=(r-l+\sum_{i\in[l,r]}a_i)+\min(f(l,k),f(k+1,r)) \]

时间复杂度 \(O(n+(R-L)^3)\)

三、总结

二叉搜索树可以转成笛卡尔树建立,这启示我们简单结构可以从复杂结构反推

树重排规划问题联想区间 \(dp\),因为它的枚举断点过程其实就是树枚举根的过程。

#include <cstdio>
#include <iostream>
using namespace std;
const int M = 100005;
#define ll long long
int read()
{
    int num=0,flag=1;char c;
    while((c=getchar())<'0'||c>'9')if(c=='-')flag=-1;
    while(c>='0'&&c<='9')num=(num<<3)+(num<<1)+(c^48),c=getchar();
    return num*flag;
}
int n,m,L,R,a[M],b[M],st[M],siz[M],ch[M][2];
ll f[205][205],s[M],ans;
void dfs(int u)
{
	if(!u || b[u]>R) {s[++m]=siz[u];return ;}
	dfs(ch[u][0]);dfs(ch[u][1]);
}
ll solve(int x)
{
	m=0;dfs(x);
	for(int i=1;i<=m;i++) s[i]+=s[i-1];
	for(int i=m;i>=1;i--)
		for(int j=i+1;j<=m;j++)
		{
			f[i][j]=1ll<<60;
			for(int k=i;k<j;k++)
				f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]);
			f[i][j]+=j-i+s[j]-s[i-1];
		}
	return f[1][m];
}
signed main()
{
	n=read();
	for(int i=1;i<=n;i++)
		a[i]=read(),b[a[i]]=i;
	L=read();R=read();
	for(int i=1;i<=n;i++)
	{
		while(m && b[st[m]]>b[i]) ch[i][0]=st[m--];
		if(m) ch[st[m]][1]=i;
		st[++m]=i;
	}
	for(int i=n;i>=1;i--)
		siz[a[i]]=siz[ch[a[i]][0]]+siz[ch[a[i]][1]]+1;
	ans=L==1?solve(a[1]):0;
	for(int i=1;i<=n;i++) if(b[i]<L || R<b[i])
	{
		ans+=siz[i];
		if(L<=b[ch[i][0]] && b[ch[i][0]]<=R)
			ans+=solve(ch[i][0]);
		if(L<=b[ch[i][1]] && b[ch[i][1]]<=R)
			ans+=solve(ch[i][1]);
	}
	printf("%lld\n",ans);
}
posted @ 2021-08-29 09:09  C202044zxy  阅读(218)  评论(0编辑  收藏  举报