4446. [SCOI2015]小凸玩密室【樹形DP】
阿新 • • 發佈:2018-11-01
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
Sample Input
35 1 2
2 1
Sample Output
5HINT
對於100%的資料,1≤N≤2×10^5,1<Ai,Bi≤10^5
神仙樹形DP
一條合法的行走路徑,一定是先走完一個點的子樹,再訪問它的兄弟的子樹,訪問完了就回到父節點。以此類推。
也就是說要求從某個點出發,訪問完其子樹後回到某個祖先的最小代價。
設$f[x][i]$表示從$x$開始訪問完$x$的子樹後再走到深度為$i$的祖先(設為$kfa$)的最小代價。
設$g[x][i]$表示從$x$開始訪問完$x$的子樹後再走到深度為$i$的祖先的另外一個兒子的最小代價。
$DP$完了之後列舉最開始先點亮哪個燈然後統計答案。因為是完全二叉樹所以時空都是$nlogn$
轉移見程式碼,手畫個圖對比著理解效果應該會好一點……
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 namespacestd; 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\n",ans); 59 }