虛樹 sdoi2011《消耗戰》

2022-05-31 07:30:15 字數 3458 閱讀 7566

卡著時間過得,大概是因為全用了ll,時間漲了一倍吧??

懶得改了,第一道虛樹還是思路比較重要

下面這段文字是複製來的:

給出一棵樹.

每次詢問選擇一些點,求一些東西.這些東西的特點是,許多未選擇的點可以通過某種方式剔除而不影響最終結果.

於是就有了建虛樹這個技巧.....

我們可以用log級別的時間求出點對間的lca....

那麼,對於每個詢問我們根據原樹的資訊重新建樹,這棵樹中要盡量少地包含未選擇節點. 這棵樹就叫做虛樹.

接下來所說的"樹"均指虛樹,原來那棵樹叫做"原樹".

構建過程如下:

按照原樹的dfs序號(記為dfn)遞增順序遍歷選擇的節點. 每次遍歷節點都把這個節點插到樹上.

首先虛樹一定要有乙個根. 隨便扯乙個不會成為詢問點的點作根.

維護乙個棧,它表示在我們已經(用之前的那些點)構建完畢的虛樹上,以最後乙個插入的點為端點的dfs鏈.

設最後插入的點為p(就是棧頂的點),當前遍歷到的點為x.我們想把x插入到我們已經構建的樹上去.

求出lca(p,x),記為lca.有兩種情況:

1.p和x分立在lca的兩棵子樹下.

2.lca是p.

(為什麼lca不能是x?

因為如果lca是x,說明dfn(lca)=dfn(x)對於第二種情況,直接在棧中插入節點x即可,不要連線任何邊(後面會說為什麼).

對於第一種情況,要仔細分析.

我們是按照dfs序號遍歷的(因為很重要所以多說幾遍......),有dfn(x)>dfn(p)>dfn(lca).

這說明什麼呢? 說明一件很重要的事:我們已經把lca所引領的子樹中,p所在的子樹全部遍歷完了!

簡略的證明:如果沒有遍歷完,那麼肯定有乙個未加入的點h,滿足dfn(h)我們按照dfs序號遞增順序遍歷的話,應該把h加進來了才能考慮x.

這樣,我們就直接構建lca引領的,p所在的那個子樹. 我們在退棧的時候構建子樹.

p所在的子樹如果還有其它部分,它一定在之前就構建好了(所有退棧的點都已經被正確地連入樹中了),就剩那條鏈.

如何正確地把p到lca那部分連進去呢?

設棧頂的節點為p,棧頂第二個節點為q.

重複以下操作:

如果dfn(q)>dfn(lca),可以直接連邊q->p,然後退一次棧.

如果dfn(q)=dfn(lca),說明q=lca,直接連邊lca->p,此時子樹已經構建完畢.

如果dfn(q)q,退一次棧,再把lca壓入棧.此時子樹構建完畢.

如果不理解這樣操作的緣由可以畫畫圖.....

最後,為了維護dfs鏈,要把x壓入棧. 整個過程就是這樣.....

題解:

對於這一道題目,樸素的樹形dp是nm的

而容易發現,某些路徑中的點是沒有用的,所以考慮建立一顆虛樹

兩條邊之間的權值就是實際權值的最小值

當然這題會發現有個性質就是如果某個點在另乙個點的子樹中,這個點是不用考慮的(事實上這個優化對效率並沒有什麼卵用)

至於那個dp,沒什麼難度,就看**吧

**:

#include using

namespace

std;

#define maxn 1000000

#define maxn2 300000

#define inf 1e15

#define ll long long

struct

rea[maxn],a2[maxn];

ll gg,m,n,l,l1,number[maxn],dep[maxn],head[maxn],

head2[maxn],ql[maxn],bz1[maxn2][

22],bz2[maxn2][22

],nowtime[maxn],now,f[maxn],b[maxn],k,st[maxn],top;

void

dfs(ll x,ll fa)

u=a[u].a;

}}bool

cmp(ll x,ll y)

ll lca(ll x,ll y)

ll query(ll x,ll y)

ans=min(ans,bz2[x][0

]); ans=min(ans,bz2[y][0

]);

return

(ans);

}void

dp(ll x,ll fa)

u=a2[u].a;

}if (nowtime[x]!=gg) f[x]=min(f[x],tmp);

}void

arr(ll x,ll y,ll z)

void

arr2(ll x,ll y)

deque

q;void

solve()

arr2(st[top-1

],st[top]);

arr2(st[top],st[top-1]); top--;

}//if (st[top]!=now) //這句是為了防止有相同點 這題裡可以不需要

st[++top]=now;

}while (top>1

)

f[1]=inf;dp(1,0

); cout

<1]<}int

main()

for (ll i=0;i<=20;i++)

for (ll j=0;j<=maxn2-10;j++) bz2[j][i]=inf;

dfs(

1,0);

for (ll i=1;i<=20;i++)

for (ll j=1;j<=n;j++)

cin>>m;

for (gg=1;gg<=m;gg++)

sort(b+1,b+1+

k,cmp);

solve();

}}

top=0

; st[++top]=1

;

while (!q.empty())

arr2(st[top-1

],st[top]);

arr2(st[top],st[top-1]); top--;

}if (st[top]!=now) st[++top]=now;

}

while (top>1)

真正和虛樹有關的就上面這麼一點,基本背板子就可以了

自己歸納的虛樹步驟

首先找乙個不可能出現的點作為第乙個節點

然後對於新加入的點,求出其與st[top]的lca

當lca>=st[top-1]的時候 就連邊st[top-1]------->lca 結束此次迴圈

當lcast[top] 然後遞迴一下

做完後判斷新加入的點是否存在,如果不存在就加入到棧中

最後再將剩餘的連完

這個步驟的原理上面已經說了 感覺理解也是挺直觀的

SDOI2011 消耗戰 虛樹

有m次詢問,又有詢問的總的點數之和是小於等於5e5的,所以,其實就是乙個虛樹的模板了,直接用棧維護乙個虛樹即可。期間寫的時候出現了一點問題 初始化的時候,不只是要初始那些輸入的k個結點,還有k個結點的lca的衍生結點也是需要初始化的,所以初始化不到位會mle和tle的,這裡不要忘。include i...

SDOI2011 消耗戰 (虛樹)

題意 給一棵n個頂點的樹,每條樹邊有邊權。m次詢問,每次詢問給出k個點,問使得這k個點均不與1號點 根節點 相連的最小代價 解法 虛樹用法 在單次詢問只涉及樹中少量節點時,可以建立一顆只包含關鍵節點的樹 將無用節點組成的鏈簡化為邊或者刪掉,形成虛樹,最後在虛樹上進行dp 關鍵點為詢問點和lca 虛數...

SDOI2011 消耗戰 虛樹 樹形動規)

虛樹的主要思想 所以怎麼辦 q.clear int m scanf d m for int i 1 i m i sort q.begin q.end cmp for int i 0 iq是乙個vector,我們開始先對所有節點按尤拉序 即深度優先搜尋是訪問的順序 排序,然後對每兩個相鄰的節點將lca...