[bzoj4446]小凸玩密室
Description
小凸和小方相约玩密室逃脱,这个密室是一棵有n个节点的完全二叉树,每个节点有一个灯泡。点亮所有灯泡即可
逃出密室。每个灯泡有个权值Ai,每条边也有个权值bi。点亮第1个灯泡不需要花费,之后每点亮1个新的灯泡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个数,代表最少的花费。
做法比较清奇。
先考虑满二叉树的情况
经过模拟,观察后我们不难发现,每次走到一个子树就一定会在其叶子节点结束,所以对于左右两个子树,可能的方案只有四种:
1.左边开始(任意节点),将左边节点遍历(最后停在一个叶子节点上)然后到根节点,然后遍历右边(停在叶子节点上)
2.(1)的方案反过来
3.中间开始,遍历左(根节点开始)(停在叶子节点上),遍历右(根节点开始)(停在叶子节点上)
4.(3)的方案反过来
考虑到我们最终都会停在叶子节点上,但起始节点不同,我们可以设计状态fu[i],fd[i]分别当前子树下表示起始节点一定在根节点上/不一定在根节点上,在i节点结束的最小花费。
然后我们不难发现,同一层二叉树下的叶子节点个数是相同的,我们也不难发现左边与右边的转移是只需要取出最小值就行。所以我们可以在O(叶子节点个数)的时间内转移,考虑到这是一个二叉树,时间复杂度是nlogn,可以通过
现在考虑完全二叉树的情况,一种方法是直接维护每个子树下的叶子节点都是什么,这样是可以维护的,我们也可以直接将这个二叉树补成满二叉树点是a为0,b为0的(这样不会改变答案),但是我们注意到在最后一个节点是左儿子时,他的右儿子的计算会出问题,于是我们考虑将这个节点补成a为0,b与左儿子相等的(这样到左儿子为终点的答案会等于在左儿子的基础上再跳到右儿子,答案是不会减少的)。需要注意的是我们还需要特判从左儿子开始的情况。
- 代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=4e5+5;
int n;
ll a[N],b[N];
int l[N],r[N];
const ll INF=1e18+1;
int pst=0;
bool chose[N];
inline void spjinit(){
int cur=pst;
while(cur!=1){
int fa=cur/2;
int nxtson=fa*2==cur?fa*2+1:fa*2;
if(nxtson<=pst)chose[nxtson]=true;
cur=fa;
}
}
inline void init(int x){
if(x<<1>n){l[x]=r[x]=x;return;}
init(x<<1);init(x<<1|1);
if(x<<1==pst)b[x<<1|1]=b[x<<1],spjinit();
l[x]=l[x<<1],r[x]=r[x<<1|1];
}
ll spj,d[N],fd[N],fu[N];
inline void dp(int x){
if(x<<1>n)return;
dp(x<<1);
dp(x<<1|1);
ll L=INF,L1=INF;
ll R=INF,R1=INF;
ll L3=INF,R3=INF;
for(int i=l[x<<1];i<=r[x<<1];i++){
d[i]+=b[x<<1];
L=min(L,fd[i]+d[i]*a[x]);
L1=min(L1,fu[i]+(b[x<<1|1]+d[i])*a[x<<1|1]+b[x<<1]*a[x<<1]);
L3=min(L3,fu[i]+b[x<<1]*a[x<<1]+(b[x]+d[i])*a[x/2]);
}
for(int i=l[x<<1|1];i<=r[x<<1|1];i++){
d[i]+=b[x<<1|1];
R=min(R,fd[i]+d[i]*a[x]);
R1=min(R1,fu[i]+(b[x<<1]+d[i])*a[x<<1]+b[x<<1|1]*a[x<<1|1]);
R3=min(R3,fu[i]+b[x<<1|1]*a[x<<1|1]+(b[x]+d[i])*a[x/2]);
}
for(int i=l[x<<1];i<=r[x<<1];i++){
fd[i]=fu[i]+R+b[x<<1]*a[x<<1];
}
for(int i=l[x<<1|1];i<=r[x<<1|1];i++){
fd[i]=fu[i]+L+b[x<<1|1]*a[x<<1|1];
}
for(int i=l[x<<1];i<=r[x<<1];i++){
fu[i]=R1+fu[i];
fd[i]=min(fd[i],fu[i]);
}
for(int i=l[x<<1|1];i<=r[x<<1|1];i++){
fu[i]=L1+fu[i];
fd[i]=min(fd[i],fu[i]);
}
if(chose[x<<1])spj+=L3;
if(chose[x<<1|1])spj+=R3;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%lld",&a[i]);
for(int i=1;i<n;i++)scanf("%lld",&b[i+1]);
int tot=0;
while((1<<tot)-1<n)tot++;
pst=n;
n=(1<<tot)-1;
init(1);
dp(1);
int fa=pst/2;
spj+=a[pst/2]*b[pst]+a[fa/2]*b[fa];
ll ans=INF;
for(int i=l[1];i<=r[1];i++){
ans=min(ans,fd[i]);
}
if(pst%2==0)ans=min(ans,spj);
printf("%lld\n",ans);
}