SCOI2015 小凸玩密室

2021-09-01 02:18:25 字數 3199 閱讀 7734

演算法難度5,思維難度7,**難度6

給定一棵n

nn個點的完全二叉樹,樹邊帶權,點帶val

(x

)val(x)

val(x)

的權值。

定義兩個點的距離dis

(u,v

)dis(u,v)

dis(u,

v)為u

uu到v

vv的最短路徑上經過的邊權之和。

你一開始可以選擇乙個起始點,並點亮這個點,這一步不需要花費。

你在點亮乙個點之後,必須緊接著將它的子樹內所有點都點亮,才可以點亮其他點。

同時你需要滿足點亮的點時刻在乙個連通塊內。

除了第一步,你點亮其他點所需的代價為dis

(u,v

)×va

l(v)

dis(u,v)\times val(v)

dis(u,

v)×v

al(v

),其中u

uu是你上一次點亮的點,v

vv是你這次點亮的點。

1 ≤n

≤2×1

05,1

≤val

≤105

1\le n\le 2\times 10^5,1\le val\le 10^5

1≤n≤2×

105,

1≤va

l≤10

5我們考慮如何dp這個東西。

首先要考慮如果確定乙個起點,答案會是什麼樣子的。

答案一定是:先每次選擇乙個兒子,填完這棵子樹,再出來填另一棵;填完這個點為根的子樹後,向父親走,再去填另一棵子樹,這樣往復直到填完整棵樹。

那麼我們來考慮如何用設計dp狀態,使得能用這個dp來湊出答案來。

假如我們設dp(

x)

dp(x)

dp(x

)表示填完x

xx的子樹所需的最小代價呢,顯然這東西沒法轉移,因為你不知道你從哪兒來。

假如來個dp(

i,j)

dp(i,j)

dp(i,j

)表示填完i

ii的子樹且最後停在j

jj的最小代價呢,顯然狀態數太過龐大。

我們換個思路,既然沒法記錄從哪兒來,不如記錄我們下一步去哪兒?

我們用dp(

i,j)

dp(i,j)

dp(i,j

)表示填完i

ii的子樹,且最後去點亮j

jj的最小代價,發現合法狀態數非常少,是o(n

logn

)o(nlogn)

o(nlog

n)級別的。

因為每個點填完子樹之後,下一步只能點亮乙個祖先或者祖先的另乙個兒子。

具體來定義一下這個dp狀態吧:dp(

i,j,

0/1)

dp(i,j,0/1)

dp(i,j

,0/1

)代表填完i

ii的子樹,0

00代表下一步填j

jj級祖先,1

11代表下一步填j+1

j+1j+

1級祖先的另乙個兒子,也就是j

jj級祖先的兄弟。

我們發現這個dp狀態可以涵蓋所有需要的資訊,轉移雖然有點兒複雜,但卻十分自然。

來具體說一下狀態轉移吧:

如果這個點是葉子,也就是它沒有兒子,那麼只需要直接計算跳躍的代價就好了。

如果這個點有左兒子,那麼只需要先走下去並計算這一步的代價,然後從左兒子跳到j+1

j+1j+

1級祖先即可。

如果這個點有左右兒子,那麼就對於先走左邊還是先走右邊分類討論,然後取min即可。

這樣我們就可以處理出來所有dp值了。

考慮有了這些dp值,我們如何計算最終答案。

考慮列舉從哪個點開始,然後模擬填的過程:先每次選擇乙個兒子,填完這棵子樹,再出來填另一棵;填完這個點為根的子樹後,向父親走,再去填另一棵子樹,這樣往復直到填完整棵樹。

然而這之中會有點兒小問題,就是走到根之後很難繼續走下去了,我們發現沒有合適的狀態供我們使用了。

我們可以新增乙個虛擬節點0

00號點,它的點權為0

00,它連一條向1

11的邊,權值為000。

這樣一切問題都可以完美地解決了,因為我們的策略總是最後走到0

00號點然後停下,然而0

00號點權值為0

00,對答案毫無影響。

這樣我們就可以用o(n

logn

)o(nlogn)

o(nlog

n)的時間複雜度來解決這個題了。

我思考了一下,覺得dp的一大關鍵就是如何用狀態拼出來結果,自己體會一下吧。

寫這個題的時候需要注意邊界問題。

#include

#include

#include

#include

#include

#include

#define ll long long

using

namespace std;

inline

intread()

while

(ch>=

'0'&& ch<=

'9')x=x*10+

(ch^48)

,ch=

getchar()

;return f==

1?x:

-x;}

const

int n=

2e5+5;

int n;

ll a[n]

,val[n]

;ll st[n][20

],dis[n][20

],dp[n][20

][2]

;inline

void

solve()

for(

int i=n;i>=1;

--i)

elseif(

(i<<1|

1)>n)

else}}

ll ans=

0x3f3f3f3f3f3f3f3f

;for

(int i=

1;i<=n;

++i)

ans=

min(ans,tmp);}

printf

("%lld\n"

,ans);}

intmain()

SCOI2015 小凸玩密室

題目描述 小凸和小方相約玩密室逃脫,這個密室是一棵有 n 個節點的完全二叉樹,每個節點有乙個燈泡。點亮所有燈泡即可逃出密室。每個燈泡有個權值 a i 每條邊也有個權值 b i 點亮第 1 個燈泡不需要花費,之後每點亮乙個新的燈泡 v 的花費,等於上乙個被點亮的燈泡 u 到這個點 v 的距離 d u,...

SCOI2015 小凸玩密室 解題報告

雖然有心裡在想一些奇奇怪怪的事情的原因,不過還是寫太久了.不過這個題本身也挺厲害的 注意第乙個被點亮的是任意選的,我最開始壓根沒注意到 dp 代表 i 號點子樹最後連出去的乙個點連的是它第 j 層的祖先 f 代表 i 號點子樹最後連出去的乙個點連的是它第 j 層祖先的另乙個兒子 然後我們就可以拼子樹...

SCOI2015 小凸玩矩陣

題目很明顯的乙個事情,求第 k 大的最小值,一般採用的做法是二分 有人會問,但是不滿足二分性啊,接著往下看 考慮二分答案,但是如何檢驗 mid 是對還是錯,考慮每次只能取 mid 的數字,如果能取到 n k 1 個數字以上就可以,至於看能否取到 n k 1 個,採用二分圖匹配,左邊的點是行,右邊的點...