動態點分治

2022-05-25 13:48:06 字數 3724 閱讀 8010

首先你要先會點分治,然後動態點分治就是把點分治可持久化一下,讓其不用再每次詢問時都重新做一遍

具體就是,你考慮做點分的時候,你在每個分治重心上統計它所管轄的所有點的資訊,並計算答案。那麼每個點的資訊只會出現在它上面的分治重心中,所以我們把每層分治重心向上一層的連邊,這樣每個點的資訊只會出現都在它所有的祖先中,然後你維護需要的資訊,每次修改就往祖先跳,由於點分治只有\(log\)層,所以這顆點分樹高度只有\(log\),可以接受。

維護的資訊根據每到題變化,非常靈活。

例題:zjoi2007捉迷藏

題意:有乙個\(n\)個點的樹,初始都是黑點,現在有兩種操作,一種是將乙個點反色,另一種是詢問樹上最遠的兩個黑點的距離。

題解:求樹上最長鏈我們可以使用點分解決,對於每個分治重心,我們在它所管轄的子樹裡各找一條到分治重心的最長鏈,將最長鏈和次長鏈拼起來跟新答案。

現在我們考慮維護一下,對每個點開兩個支援刪除和取最大值的資料結構,乙個存它管轄的所有點到它上一級分治重心的距離(我們稱為a),就是一條一條的鏈。另乙個存它所有下一級的分治重心到它的最長鏈(我們稱為b),然後答案就是最長鏈和次長鏈的最大值,這個答案也要開個資料結構來維護

關於這個資料結構,\(multiset\)可以,但是好像常數巨大,我們使用兩個堆來支援就行了,具體來說就是乙個存資料,另乙個存要刪除的,取最大值的時候,如果兩個堆隊首一樣就都彈掉

關於修改,例如開燈,也就是這個點不能用,對於他自己,把b裡面的中的\(0\)給去掉,防止答案是一條以它為端點的鏈,對於它每個點分樹上的父親\(y\),從\(y\)的a中刪去這條鏈長,再處理一下這次變動對\(fa[y]\)的b的影響即可

每次改動\(b\)時,要先將答案陣列中他的貢獻刪掉,改完再加回去。

根本不能看的**(下面還有一道例題)

// luogu-judger-enable-o2

#includeusing namespace std;

typedef int sign;

typedef long long ll;

#define for(i,a,b) for(register sign i=(sign)a;i<=(sign)b;++i)

#define fordown(i,a,b) for(register sign i=(sign)a;i>=(sign)b;--i)

const int n=1e5+5;

templatebool cmax(t &a,t b)

templatet read()

templatevoid write(t x,char y)

if(x<0)

static char wr[20];

int top=0;

for(;x;x/=10)wr[++top]=x%10+'0';

while(top)putchar(wr[top--]);

putchar(y);

}void file()

int n;

vectore[n];

#define pb push_back

void input()

}typedef priority_queueq;

struct pq

void erase(int x)

int top()

return q.top();

}void pop()

q.pop();

}int sec()

int size()

}a[n],b[n],ans;

//multiseta[n],b[n],ans;

const int inf=0x3f3f3f3f;

int fa[n],size[n],rt,min,sum;

bool ban[n];

void get_root(int u,int pre)

cmax(max,sum-size[u]);

if(cmin(min,max))rt=u;

}void get_dep(int u,int pre,int dis)

return st[x][0];

}int dis(int x,int y)

char opt[20];

int m,cl[n],cnt;

void on(int x)

//insert(b[x]);

//b[x].erase(b[x].lower_bound(0));

//cerr<1?b[fa[y]].sec():-1;

//erase(b[fa[y]]);

if(a[y].size())b[fa[y]].erase(a[y].top());

a[y].erase(dis(x,fa[y]));

// cout<<"dis"<<' '<1?b[fa[y]].sec():-1;

if(t1!=t2)

}}void off(int x)

for(int y=x;fa[y];y=fa[y])

}}void work()

else

}else

// cout題意:一顆\(n\)個點的樹,每個點有點權,要求支援修改乙個點權和查詢距離某個點距離不超過\(k\)的點的點權和

題解:每個點開兩個樹狀陣列,分別維護距它不超過多少的點權之和,和它子樹中距離它父親不超過多少的點權之和

這樣在詢問時對於每乙個分治重心\(y\),貢獻就是不超過\(k-dis(x,y)\)的點權和減去不超過\(dis(x,fa[y])\)的點權和,減去的這部分會在他的父親被算回來。

#includeusing namespace std;

typedef int sign;

typedef long long ll;

#define for(i,a,b) for(register sign i=(sign)a;i<=(sign)b;++i)

#define fordown(i,a,b) for(register sign i=(sign)a;i>=(sign)b;--i)

const int n=1e5+5;

templatebool cmax(t &a,t b)

templatet read()

templatevoid write(t x,char y)

if(x<0)

static char wr[20];

int top=0;

for(;x;x/=10)wr[++top]=x%10+'0';

while(top)putchar(wr[top--]);

putchar(y);

}void file()

int n,m;

int val[n];

vectore[n];

#define pb push_back

void input()

}bool ban[n];

int rt,min,sum,size[n];

void get_root(int u,int pre)

cmax(max,sum-size[u]);

if(cmin(min,max))rt=u;

}int fa[n][21],cnt[n],dis[n][21];

vectors1[n],s2[n];

void get_dep(int u,int pre,int g,int dis)

}int sz;

void solve(int u)

void insert(int x,int v)

}int main()

點分治 動態點分治

實在拖得太久了。先扔掉資料 分治的核心是盡量把乙個整體分成接近的兩個部分,這樣遞迴處理可以讓複雜度從n 變成nlogn。兩個問題,如何區分和如何算答案。對於第乙個問題,重心,然後就是找重心的方法,兩個dfs,對於第二個問題,對於每個重心算當前塊中每個點到重心的答案,然後由重心分開的塊要把多餘的資訊去...

點分治與動態點分治

點分治一般是用於解決樹上路徑問題。樹的重心 把重心這個點割掉後,使所形成的最大的聯通塊大小最小的點。可以證明重心子樹的大小最大不會超過 n over 2 重心可以通過 dfs 一遍求出。maxsiz x 表示割掉點x後所形成的的最大的聯通塊的大小 void dfs int x,int fa max ...

動態點分治

由於蒟蒻太遜,現在才開始學動態點分治,寫乙個 blog 吧。動態點分治是利用點分治的過程,建成一顆由子樹重心連線而成的點分樹,這棵樹的高度為 log n 級別的,因此可以通過暴力跳父親完成修改操作。建立點分樹的過程,就是按照點分治的流程,記錄上級重心並連線,即可獲得一棵點分樹。點分樹的結構與原樹不相...