例題:bzoj3572/洛谷p3233
如果,只是考慮每個點被誰控制的話,用兩個dfs就能夠搞定了。
可是這題n和q都很大,只有∑
m\sum m
∑m相對較小,因此我們應該考慮一種基本上只跟詢問點有關的演算法。
那就是虛樹。
現在我們有若干詢問點,一棵以1為根的樹。為了能夠將詢問點按照原本的樹上路徑連成一棵新樹,我們還需要將這些詢問點按照dfs序排序後,相鄰兩個詢問點之間的lca,這些點組成的就是虛樹(姑且稱虛樹上的點為「神選之點標記點」)。最後我們把這些標記點按照原樹裡的輩分關係連成一棵新樹,這就是美麗的原諒色虛樹。
此題中,為了維護整棵樹的資訊,1號根節點也是需要的。
考慮一種較快的建立虛樹的方法——利用棧維護一條鏈。
首先我們求一遍dfs序,按照dfs序處理詢問點,開乙個棧。設棧頂的點的節點叫做a,棧頂第二個點叫做b,當前我們想要將乙個點x插入虛樹。
我們在棧裡面維護了一條鏈,棧頂是鏈上深度最大的節點。如果x在這個鏈上,操作很簡單,直接將x推入棧中即可。否則,我們要讓這個鏈「拐彎」朝向x。
所謂拐彎就是圖1變成圖2(圖源xzy大佬)
首先我們求出a和x的lca,o。
然後我們比較o和b的深度關係。如果b的深度更深,由於我們是按照dfs序處理的,所以b就是a的父親,連線b和a,然後將a從棧中彈出,繼續向上去尋找x與這條鏈的「拐彎」之地。
否則,比較a和o的深度關係,如果a比較深,說明o是a的父親,a出棧,退出(因為此時x和a在o的兩個不同子樹中,說明o就是那個「拐彎點」)。否則說明a=x,直接退出。
棧中點兩兩連邊。
寫成**就是醬紫的:
if
(bel[1]
!=1) s[
++top]=1
;for
(int i=
1;i<=m;
++i)
else
break;}
if(s[top]
!=o) s[
++top]
=o; s[
++top]
=x;}
while
(top>1)
add(s[top-1]
,s[top]),
--top;
建完虛樹後,我們還要處理這道題中的詢問。
首先利用兩遍dfs把虛樹上的點被哪個詢問點控制都搞一遍。
然後列舉虛樹上的每一條邊。
如果兩邊的標記點被同乙個詢問點控制,說明在原樹上找到這兩個標記點,他們中間的所有點都被這個詢問點控制。
否則,我們可以用類似倍增的方法找到兩個詢問點控制點的「國界點」來分這條邊以及這條邊上連的沒有標記點的子樹。
最後可能還有一些並不處於兩個標記點之間的點沒有歸宿。我們可以讓離他們最近的標記點來代表他們,他們的控制點就是那個代表點的控制點。
寫成**就是這樣(rem是這個點可以代表多少點,初始值為這個點的子樹大小sz)
void
cal(
int x,
int y)
for(
int i=
19;i>=0;
--i)
ans[bel[x]]+
=sz[tmp]
-sz[mid]
; ans[bel[y]]+
=sz[mid]
-sz[y]
;}
至此,這道題被解決了,下面要做的就是抄hzwer**寫**啦!
#include
using
namespace std;
intread()
const
int n=
300005
;int n,q,m,tot,now,top;
int a[n]
,b[n]
,bel[n]
,s[n]
,p[n]
,rem[n]
,ans[n]
;//bel:歸誰管 rem:每個點可以代表多少個點
int h[n]
,ne[n<<1]
,to[n<<1]
,pos[n]
,f[n][21
],dep[n]
,sz[n]
;void
add(
int x,
int y)
void
dfs(
int x,
int las)
intlca
(int x,
int y)
intcmp
(int x,
int y)
intdis
(int x,
int y)
void
dfs1
(int x)
}void
dfs2
(int x)
}void
cal(
int x,
int y)
for(
int i=
19;i>=0;
--i)
ans[bel[x]]+
=sz[tmp]
-sz[mid]
; ans[bel[y]]+
=sz[mid]
-sz[y];}
void
work()
else
break;}
if(s[top]
!=o) s[
++top]
=o; s[
++top]
=x;}
while
(top>1)
add(s[top-1]
,s[top]),
--top;
dfs1(1
),dfs2(1
);//求出每個點被誰管
for(
int j=
1;j<=now;
++j)
for(
int i=h[p[j]
];i;i=ne[i]
)cal
(p[j]
,to[i]);
for(
int i=
1;i<=now;
++i) ans[bel[p[i]]]
+=rem[p[i]];
for(
int i=
1;i<=m;
++i)
printf
("%d "
,ans[b[i]])
;puts(""
);for(
int i=
1;i<=now;
++i) h[p[i]
]=bel[p[i]
]=ans[p[i]]=
0;//防止時間**的清空方式
}int
main()
bzoj3572又TM是網路流
我承認我寫網路流寫瘋了 我承認前面幾篇博文都是扯淡,我寫的是垃圾dinic 根本不叫dinic 我承認這道題我調了半天 我承認我這道題一開始是t的,後來換上真正的dinic才過 我承認我還沒理解dinic 我承認我一直到處改最後才發現輸入順序錯了 我承認我已經瘋了 我承認我寫我承認上癮了 回正題 這...
虛樹學習筆記
將關鍵點按dfs序排序後,所有關鍵點與相鄰關鍵點的lca合起來構成虛樹 通常還要加上整棵樹的根 虛樹至多有2k2k 個點。體現在實現中就是每次都pop若干點,並有機會push2個點。stk中存的是從根到當前點的遞迴棧中目前選入虛樹的點。stk中的點之間都未連邊 因為事實上關係還未確定 pop掉乙個點...
虛樹 學習筆記
水平不夠,學習來湊 又開了個天大的新坑 sdoi 2011 消耗戰 題目大意就是講 給出一棵樹,有邊權,然後給出k個查詢點,問從1號店不能到任何乙個查詢點的代價是多少.先考慮一下樹形動歸.dp i 表示從1不能到以i為根的子樹中的所有查詢點的最小代價 考慮維護乙個量,mins i 表示從1到i路徑最...