洛谷 P4253 [SCOI2015]小凸玩密室

题目描述

小凸和小方相约玩密室逃脱,这个密室是一棵有 n n n 个节点的完全二叉树,每个节点有一个灯泡。点亮所有灯泡即可逃出密室。每个灯泡有个权值 a i a_i ai,每条边也有个权值 b i b_i bi。点亮第一个灯泡不需要花费,之后每点亮一个新的灯泡 v v v 的花费,等于上一个被点亮的灯泡 u u u 到这个点 v v v 的距离 D u , v D_{u,v} Du,v,乘以这个点的权值 a v a_v av。在点灯的过程中,要保证任意时刻所有被点亮的灯泡必须连通,在点亮一个灯泡后必须先点亮其子树所有灯泡才能点亮其他灯泡。请告诉他们,逃出密室的最少花费是多少。

输入输出格式

输入格式:
1 1 1 行包含 1 1 1 个数 n n n,代表节点的个数
2 2 2 行包含 n n n 个数,代表每个节点的权值 a i a_i ai ( i = 1 , 2 , … … , n ) (i=1,2,……, n) (i=1,2,,n)
第 33 行包含 n − 1 n−1 n1 个数,代表每条边的权值 b i b_i bi,第 i i i 号边是由第 ( i + 1 ) / 2 (i+1)/2 (i+1)/2 号点连向第 i + 1 i+1 i+1 号点的边。
( i = 1 , 2 , … … , n − 1 ) (i=1,2,……, n - 1) (i=1,2,,n1)
输出格式:
输出包含 1 1 1 个数,代表最少的花费。

输入输出样例

输入样例#1:
3
5 1 2
2 1
输出样例#1:
5
说明

对于 10 % 10\% 10% 的数据, 1 ≤ n ≤ 10 1≤n≤10 1n10
对于 20 % 20\% 20% 的数据, 1 ≤ n ≤ 20 1≤n≤20 1n20
对于 30 % 30\% 30% 的数据, 1 ≤ n ≤ 2000 1≤n≤2000 1n2000
对于 100 % 100\% 100% 的数据, 1 ≤ n ≤ 2 ∗ 1 0 5 1≤n≤2∗10^5 1n2105, 1 ≤ a i , b i ≤ 1 0 5 1≤a_i,b_i≤10^5 1ai,bi105

分析:
因为点亮的灯必须是一个连通块,而且子树内也必须全部点亮才能点别的,所以点亮顺序一定是先从某个点 x x x
开始,点完他的子树,然后再点亮他的父亲,然后点他的兄弟(没有跳过),然后再点父亲的父亲……
我们设 f [ i ] [ j ] f[i][j] f[i][j] 为从点 i i i走到他的 j j j级祖先的另一个儿子的代价。
g [ i ] [ j ] g[i][j] g[i][j] 为从点 i i i走到他的 j j j级祖先的代价。
对于一个叶子节点,无任何代价就可以乱走,代价就直接是距离 ∗ * 终点的权值。
对于一个只有左儿子的节点,那么这个节点子树内一定只有两个点。那么先要走到这个左儿子,因为左儿子是也只节点,直接加上左儿子对应转移值。
对于其他节点,先考虑 f f f 值的转移。假设当前点为 x x x,我们先往左走,花费代价就是这两点距离 ∗ * 终点权值,然后再从左儿子走到右儿子,也就是 x . l s o n x.lson x.lson 1 1 1级祖先的另一个儿子,即 f [ x . l s o n ] [ 1 ] f[x.lson][1] f[x.lson][1],然后再从右儿子走到目标点的另一个儿子得到 f [ x ] [ j ] f[x][j] f[x][j],因为 x x x要走 j j j层,那么儿子就要走 j + 1 j+1 j+1层。
转移为
f [ i ] [ j ] = d i s ( i , i . l s o n ) ∗ w [ i . l s o n ] + f [ i . l s o n ] [ 1 ] + f [ i . r s o n ] [ j + 1 ] f[i][j]=dis(i,i.lson)*w[i.lson]+f[i.lson][1]+f[i.rson][j+1] f[i][j]=dis(i,i.lson)w[i.lson]+f[i.lson][1]+f[i.rson][j+1]
如果走右儿子同理,两者取 m i n min min
g g g 值也一样处理,不过这个要用到 f f f 值。
那么从 x x x点开始的答案就是走到 0 0 0 号点(假设是 1 1 1 的父亲)的代价,因为最后一步是从任意一定一点(叶子节点)结束,不好统计,又因为 0 0 0 号点的权值为 0 0 0,所以走到 0 0 0号点没有费用,所以直接可以看做答案。
复杂度是 O ( n l o g n ) O(nlogn) O(nlogn)的。

代码:

// luogu-judger-enable-o2
#include <iostream>
#include <cstdio>
#include <cmath>
#define LL long long

const int maxn=2e5+7;

using namespace std;

LL a[maxn],b[maxn],lc[maxn],rc[maxn];
LL n;
LL g[maxn][20],f[maxn][20],dis[maxn][20];
LL ans,sum;

LL brother(LL k,LL x)
{
    return (k>>(x-1))^1;
}

int main()
{
    scanf("%lld",&n);
    for (LL i=1;i<=n;i++) scanf("%lld",&a[i]);
    for (LL i=2;i<=n;i++) scanf("%lld",&dis[i][1]);	
    for (LL i=1;i<=n;i++)
    {
        if (i*2<=n) lc[i]=i*2;
        if (i*2+1<=n) rc[i]=i*2+1;
    }
    for (LL j=2;j<20;j++)
    {
        for (LL i=1;i<=n;i++)
        {
        	dis[i][j]=dis[i][j-1]+dis[i>>(j-1)][1];
        }	
    }  
    for (LL i=n;i>0;i--)
    {
        if (!lc[i])
        {
            for (LL j=1;i>>(j-1);j++) 
            {
                f[i][j]=(dis[i][j]+dis[brother(i,j)][1])*a[brother(i,j)];
            }	
        }
        else if (!rc[i])
        {
            for (LL j=1;i>>(j-1);j++) 
              f[i][j]=dis[lc[i]][1]*a[lc[i]]+f[lc[i]][j+1];
        }
        else
        {
            for (LL j=1;i>>(j-1);j++) 
              f[i][j]=min(dis[lc[i]][1]*a[lc[i]]+f[lc[i]][1]+f[rc[i]][j+1],dis[rc[i]][1]*a[rc[i]]+f[rc[i]][1]+f[lc[i]][j+1]);
        }
    }			
    for (LL i=n;i>0;i--)
    {
        if (!lc[i])
        {
            for (LL j=1;i>>(j-1);j++) 
              g[i][j]=dis[i][j]*a[i>>j];
        }
        else if (!rc[i])
        {
            for (LL j=1;i>>(j-1);j++) 
              g[i][j]=g[lc[i]][j+1]+dis[lc[i]][1]*a[lc[i]];
        }
        else
        {
            for (LL j=1;i>>(j-1);j++) 
              g[i][j]=min(dis[lc[i]][1]*a[lc[i]]+f[lc[i]][1]+g[rc[i]][j+1],dis[rc[i]][1]*a[rc[i]]+f[rc[i]][1]+g[lc[i]][j+1]);
        }
    }		   
    ans=1e18;		
    for (LL i=1;i<=n;i++)
    {
        sum=g[i][1];
        for (LL x=i>>1,j=1;x>0;x>>=1,j++)
        {
            LL fa=x>>1;
            if (brother(i,j)>n) sum+=dis[x][1]*a[fa];
                           else sum+=dis[brother(i,j)][1]*a[brother(i,j)]+g[brother(i,j)][2];
        }
        ans=min(ans,sum);
    }
    printf("%lld",ans);
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值