虛樹學習筆記(洛谷2495 消耗戰)
因為辣雞csdn,導致之前快寫好的部落格沒了
QWQ悲傷逆流成河qwqqq
首先虛樹,這個東西,我感覺是一種思想,或者是方法,而並不是一個數據結構什麼的。
他主要是用來解決:給出一棵樹,每次詢問選擇一些關鍵點,求一些資訊。
這些資訊的特點是,許多未選擇的點可以通過某種方式剔除而不影響最終結果。
於是就有了建虛樹這個演算法。我們根據原樹的資訊重新建樹,這棵樹中要儘量少地包含非關鍵節點。 這棵樹就叫做虛樹。這棵虛樹包含任意兩個關鍵節點的LCA。
通常這類題
會
飛,而
是能跑得過的QWQ
那我們應該怎麼構造虛樹呢QWQ我們把所有關鍵點按照dfs序排好序。
然後,虛樹要有一個根。這裡一般直接把1號節點設為根。構建虛樹的主要過程就是使用一個棧,維護從根開始的一條鏈。這條鏈上的所有點的dfs序一定是遞增的。
我們把關鍵點掃描一遍,一邊維護這個棧一邊連邊構建虛樹。
具體來說:
1.把根節點放入棧中2.把所有關鍵點點掃一遍。設當前的節點為 ,棧頂(鏈末端)的節點為 。求出 和 的 。
此時有2種情況。
1) 為 。此時 在 子樹內。我們就把 壓入棧,相當於直接把 新增到這條鏈的末尾。
2) 分立在 的兩個子樹中。此時y這個子樹中的所有關鍵點一定都被遍歷過了。(原因:設有一關鍵點a在y子樹中,沒有被遍歷過。則 。但是我們是把關鍵點按照dfs序來排的,a一定在x之前被掃)
因此y子樹內的所有關鍵點都已經被被加入虛樹。接下來我們要把 這一段的點加入虛樹。我們設棧頂的節點為 ,棧頂的第二個節點為 。
重複以下操作:
1.若 直接連邊 ,然後把 出棧。
2.若 這意味著 就是 。直接連邊 。此時子樹已構建完畢。
3.若 ,說明 被 和 夾在中間。此時我們必須要把 加入虛樹,所以連邊 ,然後把 彈出棧,把lca入棧。然後子樹構造完畢。
最後把棧裡面的元素搞一搞,每次 即可感覺配合程式碼會比較好理解
void solve()
{
// memset(point,0,sizeof(point));
cnt=0;
sort(a+1,a+1+k,cmp);
top=1;
sta[top]=1;
for (int i=1;i<=k;i++)
{
int l = lca(sta[top],a[i]);
if (l!=sta[top])
{
while (top>1)
{
if (dfn[sta[top-1]]>dfn[l])
{
addedge(sta[top-1],sta[top],0);
top--;
}
else
{
if (dfn[sta[top-1]]==dfn[l])
{
addedge(sta[top-1],sta[top],0);
top--;
break;
}
else
{
addedge(l,sta[top],0);
sta[top]=l;
break;
}
}
}
}
if (sta[top]!=a[i]) sta[++top]=a[i];
}
while (top>1)
{
addedge(sta[top-1],sta[top],0);
top--;
}
}
那麼剩下的主要就是 部分了
首先,我們定義 表示在原樹上 的路徑上邊權的最小值, 表示切割完 子樹內所有關鍵點的最小花費。
對於當前點 ,如果他是關鍵點,那麼必須切割這個點到1的路經上的一條邊,那麼就是 ,否則
上程式碼
int dp(int x,int flag)
{
int sum=0;
for (int &i=point[x];i;i=nxt[i])
{
int p = to[i];
sum=sum+dp(p,flag);
}
if (tag[x]==flag)
{
return mn[x];
}
return min(sum,mn[x]);
}
其中有一個要注意的地方就是虛樹的時候,因為每次要重新建樹,所以要清空 陣列,而由於時間原因,又不能直接 ,所以我們只能使用奇妙的手段!自殺式遍歷通過取地址,不斷修改
for (int &i=point[x];i;i=nxt[i])
由於宕機了三次QWQ
所以暫時沒有辦法放整個題的程式碼