P1864 [NOI2009]二叉查找树

链接P1864 [NOI2009]二叉查找树

  • 这题还是蛮难的……是我菜
  • 题目描述中的一大堆其实就是在描述\(treap.\),考虑\(treap\)的一些性质:
  • 首先不管怎么转,中序遍历是确定的,所以先按照数据值排序,变成序列问题。
  • 其次是父亲的权值比儿子小,但是这是个相对关系,所以对权值离散。
  • 问题变成了对一个中序构造一棵树使得满足\(treap\)的第二条性质。
  • \(f_{l,r,v}\)表示区间\(l,r\),最小权值大于等于\(v\)的最小代价,这种状态的好处在于我们可以快速知道一段序列的父亲应该最小要多大才可以,因为父亲的权值是一定小于所有儿子的,所以小于等于\(v\)就可以了。
  • 转移首先有:$$
    f_{l,r,v}=min(f_{l,p-1,v}+f_{p+1,r,v}+K+P);$$
  • 其中\(K\)是一次修改代价,\(P\)是区间和,\(p\)是枚举的父亲。
  • 这里是强制修改当前权值了。
  • 然后如果\(v_p>=v\),有$$f_{l,r,v}=min(f_{l,p-1,vl}+f_{p+1,r,vl}+P);$$
  • 其中\(vl\)\(p\)点权值。
  • 考虑一下边界即可,注意如果\(l>r\)\(f_{l,r,v}=0\)
#include<bits/stdc++.h>
#define R register int
#define ll long long 
using namespace std;
const int N=100;
int n,len,K,O[N];
ll num[N],f[N][N][N],ans;
struct Qs{int id,vl,pd;}w[N];
int cmpid(const Qs &x,const Qs &y){return x.id<y.id;}
int gi(){
	R x=0,k=1;char c=getchar();
	while((c<'0'||c>'9')&&c!='-')c=getchar();
	if(c=='-')k=-1,c=getchar();
	while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+c-'0',c=getchar();
	return x*k;
}
ll Get(R l,R r,R v){
	if(l>r)return 0;
	if(f[l][r][v]!=-1)return f[l][r][v];
	f[l][r][v]=1e18;ll P=num[r]-num[l-1];
	if(l==r){f[l][r][v]=P+((w[l].vl>=v)?0:K);return f[l][r][v];}
	for(R p=l;p<=r;++p){
		f[l][r][v]=min(f[l][r][v],Get(l,p-1,v)+Get(p+1,r,v)+K+P);
		if(w[p].vl>=v)
			f[l][r][v]=min(f[l][r][v],Get(l,p-1,w[p].vl)+Get(p+1,r,w[p].vl)+P);
	}
	return f[l][r][v];
}
int main(){
	freopen("s.in","r",stdin);
	n=gi(),K=gi(),ans=1e18;
	for(R i=1;i<=n;++i)w[i].id=gi();
	for(R i=1;i<=n;++i)
		w[i].vl=gi(),O[++len]=w[i].vl;
	for(R i=1;i<=n;++i)w[i].pd=gi();
	sort(w+1,w+n+1,cmpid);
	sort(O+1,O+len+1),len=unique(O+1,O+len+1)-O-1;
	for(R i=1;i<=n;++i){
		w[i].vl=lower_bound(O+1,O+len+1,w[i].vl)-O;
		num[i]=num[i-1]+w[i].pd;
	}
	memset(f,-1,sizeof(f));
	for(R i=1;i<=len;++i)ans=min(ans,Get(1,n,i));
	printf("%lld\n",ans);
	return 0;
}

posted @ 2018-10-23 19:46  Tyher  阅读(169)  评论(0编辑  收藏  举报