bzoj 4446: [Scoi2015]小凸玩密室

Description

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

Input

第1行包含1个数n,代表节点的个数
第2行包含n个数,代表每个节点的权值ai。(i=l,2,…,n)
第3行包含n-l个数,代表每条边的权值bi,第i号边是由第(i+1)/2号点连向第i+l号点的边。
(i=l,2...N-1)

Output

输出包含1个数,代表最少的花费。

Sample Input

3
5 1 2
2 1

Sample Output

5

HINT

对于100%的数据,1≤N≤2×105,1<Ai,Bi≤10^5


Source

 

dp神题,逆推所需,水到渠成;

题目给了很多信息:
1.完全二叉树,那么树高为log,且每个点最多两个儿子;

2.点亮的灯时刻是一个连通块,且必须点亮完其子树内的点才能点亮其他点;

因为第一个点是不确定的,所以我们可以尝试枚举第一个点x,然后开始点灯,因为需要满足点亮的灯时刻是一个连通块并且一定要处理完子树内才能往外走,所以点灯的顺序一定是:

x的子树->x的父亲->x的兄弟的子树->x的爸爸的爸爸...这样一直搞直到根为止;

这个时候我们就会需要一个知道一个g[x][y],表示处理完x的子树后,走到了y节点的代价(其中y为x的祖先),因为树高为log,所以第二维开一个log就可以了,表示深度为y的祖先;

我们考虑如何求g[x][y],如果x是叶子节点,那么直接走过去就好了;

否则的话就有两个选择,先走左子树还是先走右子树,如果是先走左子树的话,点完左子树后,我们需要去点亮x的右儿子,然后把右子树点完,然后从右子树中某点去点x的深度为y的祖先;

这个时候我们还需要求出处理完x的子树后,走到了x的兄弟的代价,这个我们用一个数组f[x][y],表示处理完x的子树后,走到了x的深度为y的兄弟的代价;

因为为完全二叉树,那么x的k级父亲可以直接用位运算算出来,g[x][y]和f[x][y]都能通过合并左右两子树的信息来得到;

最后我们枚举每个点用g数组来模拟覆盖的过程即可;

注意叶子节点和没有右儿子的节点特殊处理一下;

//MADE BY QT666
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long ll;
const int N=200050;
ll g[N][20],f[N][20],a[N],b[N],n,dep[N],dis[N];
int main(){
    scanf("%lld",&n);dep[1]=1;
    for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
    for(int i=2;i<=n;i++){
	scanf("%lld",&b[i]);
	dep[i]=dep[i>>1]+1;dis[i]=dis[i>>1]+b[i];
    }
    for(int x=n;x;x--){
	if((x<<1)>n){
	    for(int j=0;j<=dep[x];j++){
		int fa=(x>>(dep[x]-j+1)),y=((x>>(dep[x]-j))^1);
		f[x][j]=(dis[x]+dis[y]-2*dis[fa])*a[y];
	    }
	}
	else if((x<<1)==n){
	    for(int j=0;j<=dep[x];j++){
		int y=(x<<1);
		f[x][j]=a[y]*b[y]+f[y][j];
	    }
	}
	else {
	    for(int j=0;j<=dep[x];j++){
		int lson=(x<<1),rson=(x<<1|1);
		f[x][j]=min(a[lson]*b[lson]+f[lson][dep[lson]]+f[rson][j],a[rson]*b[rson]+f[rson][dep[rson]]+f[lson][j]);
	    }
	}
    }
    for(int x=n;x;x--){
	if((x<<1)>n){
	    for(int j=0;j<=dep[x];j++){
		int y=(x>>(dep[x]-j));
		g[x][j]=(dis[x]-dis[y])*a[y];
	    }
	}
	else if((x<<1)==n){
	    for(int j=1;j<=dep[x];j++){
		int y=(x<<1);
		g[x][j]=a[y]*b[y]+g[y][j];
	    }
	}
	else{
	    for(int j=0;j<=dep[x];j++){
		int lson=(x<<1),rson=(x<<1|1);
		g[x][j]=min(a[lson]*b[lson]+f[lson][dep[lson]]+g[rson][j],a[rson]*b[rson]+f[rson][dep[rson]]+g[lson][j]);
	    }
	}
    }
    ll ans=g[1][0];
    for(int i=2;i<=n;i++){
	ll ret=g[i][dep[i]-1];
        for(int x=i;x>1;x>>=1){
	    int y=x^1;
	    if(y>n) ret+=a[x>>2]*b[x>>1];
	    else ret+=a[y]*b[y]+g[y][dep[y]-2];
	}
	ans=min(ans,ret);
    }
    printf("%lld\n",ans);
    return 0;
}

转载于:https://www.cnblogs.com/qt666/p/7701708.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值