【BZOJ】4446: [Scoi2015]小凸玩密室 倍增+树形DP

版权声明:转载请附带链接或评论 https://blog.csdn.net/corsica6/article/details/82711433

传送门:bzoj4446


题解

倍增+DP好题。这道题的DP太神了!

感觉上这道题怎么DP向后的状态,或向前的状态都非常不好做,从Chen’s Blog学到了DP的新姿势

非常关键的一点:这是一颗完全二叉树。很多DP的优化方法都由此而来。

观察点灯的过程:

(1) 点亮一个点
(2) 选择其一个子节点递归下去,递归完毕则该子节点子树全部点亮(最后一个被点亮的必然是某个该子树内的叶子节点)
(3) 再递归另一子树
(4) 该子树已全部点亮,回溯其父节点
(5) 继续操作,直到整棵树都被点亮

由此过程受到启发:

  1. 无法有效枚举每一点是由其兄弟节点子树内哪个叶子转移而来
  2. 但可以枚举叶子转移到其某一级祖先的兄弟节点/或某一级祖先(注意这是一颗完全二叉树,层数是严格 log n 的)的最少花费。
  3. 转移到某一级祖先的兄弟节点的情况表示过程(3),转移到某一级祖先的情况表示过程(4)

现在可以列出转移数组 f [ i ] [ j ] [ 0 / 1 ] 了,设 l c i , r c i 分别表示节点 i 的左右儿子,节点 i 的第 j 个祖先为 f a [ i ] [ j ] (这里的第 j 个祖先表示第 j 靠近该点的祖先),节点 i 的第 j 个祖先的不为 i 祖先的子节点( f [ i ] [ j 1 ] 的兄弟)为 l i [ i ] [ j ]

  • f [ i ] [ j ] [ 0 ] 表示以节点 i 为根的子树全部被点亮之后再去点亮 f a [ i ] [ j ] 的最小花费

  • f [ i ] [ j ] [ 1 ] 表示以节点 i 为根的子树全部被点亮之后再去点亮 l i [ i ] [ j ] 的最小花费

f [ i ] [ j ] [ 0 / 1 ] 均假设 i 为起点(点亮 i 花费为0)。转移是 O ( 1 ) 的,分类讨论一下:

  • i 是叶子节点。
    f [ i ] [ j ] [ 0 ] = d i s t ( i , f a [ i ] [ j ] ) × a f a [ i ] [ j ] f [ i ] [ j ] [ 1 ] = d i s t ( i , l i [ i ] [ j ] ) × a l i [ i ] [ j ]

  • i 只有左儿子,只能走左儿子。
    f [ i ] [ j ] [ 0 ] = f [ l c i ] [ j + 1 ] [ 0 ] + d i s t ( i , l c i ) × a l c i
    f [ i ] [ j ] [ 1 ] = f [ l c i ] [ j + 1 ] [ 1 ] + d i s t ( i , l c i ) × a l c i

  • i 有左右儿子,分别枚举先走左/右儿子,取 m a x
    f [ i ] [ j ] [ 0 ] = m a x ( d i s t ( l c i , i ) × a l c i + f [ l c i ] [ 1 ] [ 1 ] + f [ r c i ] [ j + 1 ] [ 0 ] , d i s t ( r c i , i ) × a r c i + f [ r c i ] [ 1 ] [ 1 ] + f [ l c i ] [ j + 1 ] [ 0 ] )
    f [ i ] [ j ] [ 1 ] = m a x ( d i s t ( l c i , i ) × a l c i + f [ l c i ] [ 1 ] [ 1 ] + f [ r c i ] [ j + 1 ] [ 1 ] , d i s t ( r c i , i ) × a r c i + f [ r c i ] [ 1 ] [ 1 ] + f [ l c i ] [ j + 1 ] [ 1 ] )

f a [ i ] [ j ] , d i s t ( i , j ) 预处理即可。
注意第一个点亮的灯泡不是固定的,需要枚举起点。

枚举起点也是从当前起点不断往上点亮祖先,如果有兄弟就先点亮兄弟的子树。

时间复杂度 O ( n log n ) 感觉说得很详细了。


代码

#include<bits/stdc++.h>
#define RI register
#define ls(x) (x<<1)
#define rs(x) (x<<1|1)
#define p(i,j) (1<<(j-1)<=i? (i>>j):(-1))
#define li(i,j) ((i>>(j-1))^1)
using namespace std;
const int N=2e5+10,w=17;
typedef long long ll;

int n,a[N];
ll dis[N][20],f[N][20][2],ans,tp;

inline void upp(ll &x,ll y){x=min(x,y);} 

int main(){
    RI int i,j,k,t;
    memset(f,0x7f,sizeof(f));
    ans=f[0][0][0];
    memset(f[0],0,sizeof(f[0]));
    scanf("%d",&n);
    for(i=1;i<=n;++i) scanf("%d",&a[i]);
    for(i=2;i<=n;++i)
     scanf("%lld",&dis[i][1]);
    for(i=2;i<=n;++i)
     for(j=2;~p(i,j);++j)
      dis[i][j]=dis[i>>1][j-1]+dis[i][1];
    for(i=n;i;--i){
        for(j=1;~p(i,j);++j){
            if(ls(i)>n){
                f[i][j][0]=dis[i][j]*a[i>>j];
                f[i][j][1]=(dis[i][j]+dis[li(i,j)][1])*a[li(i,j)];
            }else if(rs(i)>n){
                f[i][j][0]=f[ls(i)][j+1][0]+dis[ls(i)][1]*a[ls(i)];
                f[i][j][1]=f[ls(i)][j+1][1]+dis[ls(i)][1]*a[ls(i)];
            }else{
                f[i][j][0]=dis[rs(i)][1]*a[rs(i)]+f[rs(i)][1][1]+f[ls(i)][j+1][0];
                upp(f[i][j][0],dis[ls(i)][1]*a[ls(i)]+f[ls(i)][1][1]+f[rs(i)][j+1][0]);
                f[i][j][1]=dis[rs(i)][1]*a[rs(i)]+f[rs(i)][1][1]+f[ls(i)][j+1][1];
                upp(f[i][j][1],dis[ls(i)][1]*a[ls(i)]+f[ls(i)][1][1]+f[rs(i)][j+1][1]);
            }
        }
    }
    for(i=n;i;--i){
        tp=f[i][1][0];
        for(j=1;~p(i,j);++j){
            k=li(i>>(j-1),1);
            if(k<=n)
               tp+=dis[k][1]*a[k]+f[k][2][0];
            else tp+=dis[i>>j][1]*a[i>>(j+1)];
        }
        ans=min(ans,tp);
    }
    printf("%lld\n",ans);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/corsica6/article/details/82711433