区间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][Root−1][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][Root−1][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][Root−1][lim]+dp[Root+1][R][lim]+sum[R]−sum[L−1])
不修改类似,这里就不复述了。
复杂度就只有 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;
}