卡著時間過得,大概是因為全用了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 usingnamespace
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=0while (top>1); 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;
}
真正和虛樹有關的就上面這麼一點,基本背板子就可以了
自己歸納的虛樹步驟
首先找乙個不可能出現的點作為第乙個節點
然後對於新加入的點,求出其與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...