[NOI2009]二叉查找树

题面在这里

题意

给出一个\(n\)个节点的二叉排序树的原始数据值\(d[i]\)、权值\(v[i]\)和访问频度\(s[i]\),你可以根据需要把结点的权值改为任何实数,但每次修改你会付出\(k\)的额外修改代价,且修改后所有结点的权值必须仍保持互不相同。
求整棵树的访问代价与额外修改代价的最小和。

数据范围

\[n\le 70,1\le k\le 3*10^7 \]

sol

根据最优二叉查找树的性质,考虑区间DP。
如果设\(f[i][j]\)表示仅考虑区间\([i,j]\)内的节点所能得到的最小代价
那么有转移方程

\[f[i][j]=\min_{k=i}^{j}{f[i][p-1]+f[p+1][j]+\sum_{l=i}^{j}{s[l]}+[(\min_{l=i}^{j}{v[l]})==v[p]]\times k} \]

(定义\(f[x][x-1]=0\))
时间复杂度为\(O(n^3)\)似乎太优秀了哈
交上去后惊喜地发现只有\(30'\).....

因为我们不仅需要考虑每一段区间合并成一棵二叉树后的最小代价,
还需要关注其节点在不同权值情况下的最小代价
因为节点的代价会影响到后面的决策,使得设置的状态不满足最优子结构

考虑节点作为当前树根被迫需要改变权值的情况只受到其子树权值最小值的影响(这段话很长,请多读几遍)因此多加一维表示区间内节点的最小值时的子问题

即设\(f[i][j][k]\)表示仅考虑区间\([i,j]\)内的节点,
使得当前所有节点的权值在离散化后都不小于\(k\)所能得到的最小代价

那么决策便是改变权值(区间内有节点权值比当前枚举到的节点的权值要小)或者不改变权值(区间内所有节点权值都小于枚举到的节点的权值);
转移方程在之前的方程中多加一维即可
时间复杂度为\(O(n^4)\)

代码

#include<bits/stdc++.h>
#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<iomanip>
#include<cstring>
#include<complex>
#include<vector>
#include<cstdio>
#include<string>
#include<bitset>
#include<cmath>
#include<queue>
#include<stack>
#include<map>
#include<set>
#define mp make_pair
#define pb push_back
#define RG register
#define il inline
using namespace std;
typedef unsigned long long ull;
typedef vector<int>VI;
typedef long long ll;
typedef double dd;
const dd eps=1e-10;
const int mod=1e8;
const int N=75;
il ll read(){
  RG ll data=0,w=1;RG char ch=getchar();
  while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
  if(ch=='-')w=-1,ch=getchar();
  while(ch<='9'&&ch>='0')data=data*10+ch-48,ch=getchar();
  return data*w;
}

il void file(){
  freopen("a.in","r",stdin);
  freopen("a.out","w",stdout);
}

struct node{int data,val,times;}t[N];
bool cmp(node a,node b){return a.data<b.data;}

int n,k,s[N],m[N][N],f[N][N];

int main()
{
    n=read();k=read();
    for(RG int i=1;i<=n;i++)t[i].data=read();
    for(RG int i=1;i<=n;i++)t[i].val=read();
    for(RG int i=1;i<=n;i++)t[i].times=read(),s[i]=s[i-1]+t[i].times;
    sort(t+1,t+n+1,cmp);
    
    memset(f,63,sizeof(f));
    for(RG int i=1;i<=n;i++)f[i][i]=t[i].times,m[i][i]=i;
    for(RG int i=1;i<=n+1;i++)f[i][i-1]=0;
    for(RG int l=2;l<=n;l++)
        for(RG int i=1;i<=n;i++)
            if(t[i+l-1].val<=t[m[i][i+l-2]].val)m[i][i+l-1]=i;
            else m[i][i+l-1]=m[i][i+l-2];
    
    for(RG int l=2;l<=n;l++)
        for(RG int i=1;i+l-1<=n;i++)
            for(RG int p=i;p<=i+l-1;p++)
                f[i][l+i-1]=min(f[i][l+i-1],f[i][p-1]+f[p+1][i+l-1]+s[i+l-1]-s[i-1]+(m[i][l+i-1]==p?0:k));
    
    printf("%d\n",f[1][n]);
                
  return 0;
}

posted @ 2018-03-21 22:05  cjfdf  阅读(186)  评论(0编辑  收藏  举报