SCOI2015 小凸玩密室
小凸玩密室
小凸和小方相约玩密室逃脱,这个密室是一棵有\(n\)个节点的完全二叉树,每个节点有一个灯泡。点亮所有灯泡即可逃出密室。每个灯泡有个权值\(a_i\),每条边也有个权值\(b_i\)。
点亮第\(1\)个灯泡不需要花费,之后每点亮一个新的灯泡\(v\)的花费,等于上一个被点亮的灯泡\(u\)到这个点\(v\)的距离\(\operatorname{dist}(u, v)\),乘以这个点的权值\(a_v\)。
在点灯的过程中,要保证任意时刻所有被点亮的灯泡必须连通,在点亮一个灯泡后必须先点亮其子树所有灯泡才能点亮其他灯泡。请告诉他们,逃出密室的最少花费是多少。
\(1 \leq n \leq 2 \times 10 ^ 5, 1 < a_i, b_i \leq 10 ^ 5\)
题解
https://www.cnblogs.com/CXCXCXC/p/5312237.html
\(f(x,y)\)表示走完\(x\)及其子树再走到\(y\)的最小代价。考虑转移方式,
-
如果\(x\)是叶子节点,那么答案就是\(a_y\operatorname{dist}(x,y)\)
-
如果\(x\)只有左孩子,就先遍历左子树,状态转移是\(a_{\text{lc}}b_{\text{lc}}+f(\text{lc},y)\)
-
如果两个孩子都有,就考虑是先转移到左孩子还是右孩子,\(f(x,y)=\min(a_{\text{lc}}b_{\text{lc}}+f(\text{lc},\text{rc})+f(\text{rc},y),a_{\text{rc}}b_{\text{rc}}+f(\text{rc},\text{lc})+f(\text{lc},y))\)
题目数据范围很大,这样的转移肯定在时间和空间上都不行。
但是发现很多\(x,y\)是不合法的,比如\(y\)肯定不能是\(x\)的子节点。由此考虑有意义的\(x,y\),可以发现,\(y\)一定是某个\(x\)的祖先的另一个儿子,
所以重新定义\(f(x,i)\)为走完\(x\)及其子树到深度为\(i\)的节点的子节点的最小代价。
设\(g(x,i)\)表示走完\(x\)的子树再走到\(x\)的深度为\(i\)的祖先的最小代价。转移:
-
叶子:\(g(x,i)=a_y\operatorname{dist}(x,y)\)
-
只有左孩子:\(g(x,i)=a_{\text{lc}}b_{\text{lc}}+g(\text{lc},i)\)
-
左右孩子都有:\(g(x,i)=\min(a_{\text{lc}}b_{\text{lc}}+f(\text{lc},\text{dep}_x)+g(\text{rc},i),a_{\text{rc}}b_{\text{rc}}+f(\text{rc},\text{dep}_x)+g(\text{lc},i))\)
最后就是统计答案了,我们选定一个节点\(x\)作为第一个点亮的灯,然后统计其子树的答案,再走到\(x\)的父节点,再走\(x\)的兄弟节点……以此类推。
由于\(f\)和\(g\)的第一维都是\(n\),第二维都是\(\log n\),所以总的时间复杂度是\(O(n\log n)\)。
CO int N=2e5+10;
int a[N],b[N],dep[N],dis[N];
int f[N][19],g[N][19];
signed main(){
int n=read<int>();
for(int i=1;i<=n;++i) read(a[i]);
for(int i=2;i<=n;++i) read(b[i]);
dep[1]=1;
for(int i=2;i<=n;++i) dep[i]=dep[i>>1]+1,dis[i]=dis[i>>1]+b[i];
for(int x=n;x>=1;--x)for(int i=0;i<dep[x];++i){
int lc=x<<1,rc=x<<1|1;
int y=x>>(dep[x]-i-1)^1;
if(lc>n) f[x][i]=a[y]*(dis[x]+dis[y]-2*dis[y>>1]);
else if(rc>n) f[x][i]=a[lc]*b[lc]+f[lc][i];
else f[x][i]=min(a[lc]*b[lc]+f[lc][dep[x]]+f[rc][i],a[rc]*b[rc]+f[rc][dep[x]]+f[lc][i]);
}
for(int x=n;x>=1;--x)for(int i=0;i<=dep[x];++i){
int lc=x<<1,rc=x<<1|1;
int y=x>>(dep[x]-i);
if(lc>n) g[x][i]=a[y]*(dis[x]-dis[y]);
else if(rc>n) g[x][i]=a[lc]*b[lc]+g[lc][i];
else g[x][i]=min(a[lc]*b[lc]+f[lc][dep[x]]+g[rc][i],a[rc]*b[rc]+f[rc][dep[x]]+g[lc][i]);
}
int ans=g[1][0];
for(int i=2;i<=n;++i){
int sum=g[i][dep[i]-1];
for(int x=i;x>1;x>>=1){
int y=x^1,z=x>>1;
if(y>n) sum+=a[z>>1]*b[z];
else sum+=a[y]*b[y]+g[y][dep[z]-1];
}
ans=min(ans,sum);
}
printf("%lld\n",ans);
return 0;
}