BZOJ4446:[SCOI2015]小凸玩密室(树形DP)

Description

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

Input

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

Output

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

Sample Input

3
5 1 2
2 1

Sample Output

5

HINT

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

Solution

神仙树形DP

一条合法的行走路径,一定是先走完一个点的子树,再访问它的兄弟的子树,访问完了就回到父节点。以此类推。

也就是说要求从某个点出发,访问完其子树后回到某个祖先的最小代价。

设$f[x][i]$表示从$x$开始访问完$x$的子树后再走到深度为$i$的祖先(设为$kfa$)的最小代价。

设$g[x][i]$表示从$x$开始访问完$x$的子树后再走到深度为$i$的祖先的另外一个儿子的最小代价。

$DP$完了之后枚举最开始先点亮哪个灯然后统计答案。因为是完全二叉树所以时空都是$nlogn$

转移见代码,手画个图对比着理解效果应该会好一点……

Code

 1 #include<iostream>
 2 #include<cstdio>
 3 #define N (200009)
 4 #define LL long long
 5 #define ls (i<<1)
 6 #define rs (i<<1|1)
 7 #define kfa (i>>Depth[i]-j)
 8 using namespace std;
 9 
10 LL n,Depth[N],Dist[N],a[N],b[N],g[N][21],f[N][21],ans=1e18;
11 
12 void DP()
13 {
14     for (int i=n; i>=1; --i)
15         for (int j=0; j<=Depth[i]; ++j)
16             if (ls>n)//当前是叶子 
17             {
18                 f[i][j]=(Dist[i]-Dist[kfa])*a[kfa];
19                 g[i][j]=(Dist[i]-Dist[kfa]+b[kfa]+b[kfa^1])*a[kfa^1];
20             }
21             else if (ls==n)//只有左儿子
22             {
23                 f[i][j]=b[ls]*a[ls]+f[ls][j];
24                 g[i][j]=b[ls]*a[ls]+g[ls][j];
25             }
26             else//左右儿子都有 
27             {
28                 f[i][j]=min(
29                 b[ls]*a[ls]+g[ls][Depth[ls]]+f[rs][j],
30                 b[rs]*a[rs]+g[rs][Depth[rs]]+f[ls][j]);
31                 g[i][j]=min(
32                 b[ls]*a[ls]+g[ls][Depth[ls]]+g[rs][j],
33                 b[rs]*a[rs]+g[rs][Depth[rs]]+g[ls][j]);
34             }
35 }
36 
37 int main()
38 {
39     scanf("%lld",&n);
40     for (int i=1; i<=n; ++i)
41         scanf("%lld",&a[i]);
42     for (int i=2; i<=n; ++i)
43         scanf("%lld",&b[i]);
44     for (int i=1; i<=n; ++i)
45     {
46         Depth[i]=Depth[i>>1]+1;
47         Dist[i]=Dist[i>>1]+b[i];
48     }
49     DP(); 
50     for (int i=1; i<=n; ++i)//枚举最先点哪个灯统计答案 
51     {
52         LL now=f[i][Depth[i]-1];
53         for (int j=i; j!=1; j>>=1)
54             if ((j^1)>n) now+=b[j>>1]*a[j>>2];
55             else now+=b[j^1]*a[j^1]+f[j^1][Depth[j]-2];
56         ans=min(ans,now);
57     }
58     printf("%lld
",ans);
59 }