CSP校內集訓 ac(樹上啟發式合併)

2022-03-16 23:58:06 字數 1727 閱讀 4982

有一棵樹,每個節點\(i\)有一定的容量\(k_i\)(只能裝\(k_i\)個顏色);有\(m\)次操作,每次給\(x\)到\(1\)路徑上的所有點加上乙個顏色\(c\);修改操作完成後詢問每個節點有多少種不同的顏色\((n,m,k_i \leq 10^5)\)

30pts資料小可以直接暴力跳

另外40pts有\(k_i=10^5\)可以模擬雨天的尾巴

可以看出來對乙個點的答案有影響的操作都在它的子樹中

這種問題,顯然要麼維護顏色(值域線段樹),要麼維護時間(修改操作的順序),而值域線段樹考慮不到\(k\)的約束,只有40pts,所以考慮維護時間

於是按修改操作的順序作為下標維護線段樹,乙個點維護兩個值:\(siz\):該子樹有多少個操作;\(sum\):該子樹中實際有貢獻的運算元

什麼叫實際有貢獻的操作?就是說同乙個子樹中,如果兩個同樣顏色的操作先後發生,那後發生的操作就沒有貢獻,也就是\(siz+1\)但\(sum\)不變

但不可能每個點都建一棵線段樹,由於乙個點只考慮其子樹中的操作,線段樹可以自底向上合併,於是就成了樹上啟發式合併,保證所有操作的複雜度不基於重兒子就可以保證複雜度沒問題

與這道題思路類似,就是把\(trie\)換成了時間上的線段樹

時間複雜度\(o(nlog^2n)\)

#include#define n 100005

#define min(x,y) ((x)<(y)?(x):(y))

#define max(x,y) ((x)>(y)?(x):(y))

using namespace std;

typedef long long ll;

int n,m,q,k[n],ans[n];

int sum[n<<2],size[n<<2],sig[n<<2];

int son[n],fa[n],t[n<<2];

vector< pair> co[n];

mapmp;//顏色還有負數,cao

int colsum=0;

struct edge

edge[n<<1];int head[n],cnt=1;

void add_edge(int from,int to)

template void read(t &x)

void dfs1(int rt)

int query(int rt,int l,int r,int k)

void add(int rt)

modify(1,1,m,tim,0,1); }}

void clr(int rt)

void cv(int x,int y)

void dfs(int rt)

if(son[rt]) dfs(son[rt]);

add(rt);

for(int i=head[rt];i;i=edge[i].next)

ans[rt]=query(1,1,m,k[rt]);

if(son[rt])

}}int main()

dfs1(1);

for(int i=1;i<=n;++i) read(k[i]);

read(m);

for(int i=1;i<=m;++i)

memset(size,0,sizeof(size));

dfs(1);

read(q);

while(q--)

return 0;

}

樹上啟發式合併

解決樹上統計問題,o n log n o n log n o n lo g n 可以結合線段樹等資料結構維護深度上的資訊 部落格 入門題 const int maxn 1e5 7 const int mod 1e9 7 ll n,m,u,v,mx,sum vector int mp maxn int...

樹上啟發式合併

樹上啟發式合併,一種美妙的黑科技,可以用普通的優化讓你 n 2 變成嚴格 n log 解決一些類似 樹上數顏色,樹上查眾數 這樣的問題 首先你要知道暴力為什麼是 n 2 的 以這個圖為例 每次你從乙個節點開始向下搜,你從1節點搜到3,搜完這個子樹然後你需要把3存的col等資訊刪去再遍歷另乙個子樹才是...

樹上啟發式合併總結

某一天發現一道樹上啟發式合併裸題,但我不會寫 學習並刷了兩天的題,是時候來寫個總結了 樹上啟發式合併 dsu on tree 是乙個在o n logn o nlogn o nlog n 時間內解決許多樹上問題的有力演算法。但它的中心其實是 暴力!沒錯,它正是由暴力優化而來。我們先看一道例題 cf60...