题目描述
小凸和小方相约玩密室逃脱,这个密室是一棵有 n n n 个节点的完全二叉树,每个节点有一个灯泡。点亮所有灯泡即可逃出密室。每个灯泡有个权值 A i A_i Ai,每条边也有个权值 b i b_i bi。
点亮第 1 1 1 个灯泡不需要花费,之后每点亮一个新的灯泡 V V V 的花费,等于上一个被点亮的灯泡 U U U 到这个点 V V V 的距离 D ( u , v ) D(u, v) D(u,v),乘以这个点的权值 A v A_v Av。
在点灯的过程中,要保证任意时刻所有被点亮的灯泡必须连通,在点亮一个灯泡后必须先点亮其子树所有灯泡才能点亮其他灯泡。请告诉他们,逃出密室的最少花费是多少。
数据范围
1 ≤ N ≤ 2 × 1 0 5 , 1 < A i , B i ≤ 1 0 5 1 \leq N \leq 2 \times 10 ^ 5, 1 < A_i, B_i \leq 10 ^ 5 1≤N≤2×105,1<Ai,Bi≤105
题解
考虑暴力, f i , j f_{i,j} fi,j 表示走完 i i i 子树,直接走到 j j j 的最小花费,转移考虑用左右儿子转移。
可以发现我们只需要知道 j j j 为 i i i 的祖先或者祖先的另一个儿子的 f f f 值即可,于是复杂度降至 n l o g n nlogn nlogn 。
注意 i i i 为叶子或者 i i i 只有左儿子的情况。
代码
#include <bits/stdc++.h>
#define LL long long
using namespace std;
const int N=2e5+5;
int n;LL a[N],b[N],f[N][20],g[N][20],ans=2e18;
int main(){
scanf("%d",&n);
for (int i=1;i<=n;i++)
scanf("%lld",&a[i]);
for (int i=2;i<=n;i++)
scanf("%lld",&b[i]),b[i]+=b[i>>1];
for (int i=n,l,r;i;i--){
l=i<<1;r=l|1;
for (int j=i,x,y=1;j>1;j>>=1,y++){
x=j^1;
if (l>n) f[i][y]=a[x]*(b[i]+b[x]-(b[j>>1]<<1));
else if (r>n) f[i][y]=f[l][y+1]+a[l]*(b[l]-b[i]);
else f[i][y]=min(a[l]*(b[l]-b[i])+f[l][1]+f[r][y+1],
a[r]*(b[r]-b[i])+f[r][1]+f[l][y+1]);
}
}
for (int i=n,l,r;i;i--){
l=i<<1;r=l|1;
for (int j=i>>1,y=1;;j>>=1,y++){
if (l>n) g[i][y]=a[j]*(b[i]-b[j]);
else if (r>n) g[i][y]=g[l][y+1]+a[l]*(b[l]-b[i]);
else g[i][y]=min(a[l]*(b[l]-b[i])+f[l][1]+g[r][y+1],
a[r]*(b[r]-b[i])+f[r][1]+g[l][y+1]);
if (!j) break;
}
}
for (int j,x,i=1;i<=n;i++){
LL v=g[i][1];j=i>>1;x=i^1;
while(j){
if (x>n) v+=a[j>>1]*(b[j]-b[j>>1]);
else v+=a[x]*(b[x]-b[j])+g[x][2];
x=j^1;j>>=1;
}
ans=min(ans,v);
}
return cout<<ans<<endl,0;
}