模板(ac):啟發式合並


首先說明一點:線段樹合並不是啟發式合並。

啟發式合並的大概內容就是:把小的數據結構按照這個數據結構的正常插入方法,一個一個地暴力塞進去。

而線段樹合並顯然不是這個東西。

這道題的題解太爛了,所以耽誤了很長時間。

對於每一次操作,它只有3個參數:起始位置,作用時間,顏色。

把顏色離散化一下,讓它們的編號分布在1e5以內。也可以不離散化,略麻煩一些而已。注意有負數。

現在我們維護一個以時間為區間下標的線段樹,里面維護兩個權值,一個是第一次出現的小球的個數,另一個是所有小球的個數。

我們另開一組vector存儲發生在這個節點上的操作,需要存儲時間/顏色這2個參數。

剛開始我們讀入所有操作,直接把它放進起始位置的vector里。

然后就可以一遍dfs求解答案。考慮在每個點要做什么事。

1)遞歸求解所有子樹

2)求解所有在這個節點上的操作得到本節點的答案

3)把與自身相關的所有事件上傳給父節點

這其實是暴力思路。

我們考慮一下,如果按照上述思路,要么就是需要清空線段樹,要么就是開100000個樹爆內存。

也只有第一個能考慮優化。

如果1號節點有2個兒子,其中2號節點上有99995次操作,3號節點上有5次。

還要清空線段樹?有什么想法嗎?

對於3號節點,它暴力上傳的復雜度並不高,關鍵就是怎么處理2號節點。

如果先掃3號節點,清空,再2號節點,信息不清空,事件也暫時不上傳。

那么回溯到了1號節點,我們有一個現成的已經存好了99995個操作的線段樹,而vector里面只有5次操作需要插入。多好啊

考慮這樣一個問題:因為父節點只是一個點,而子樹里可能有很多點,所以某個兒子身上可能聚集了很多操作。

那么對於每一個節點,我們稱其“子樹中操作數最多的兒子”為它的重兒子。

那么按照剛才舉例子的這個思路,優化一下上面的那個dfs流程。

1)掃所有的輕兒子,把輕兒子的vector中的全部時間放進父節點額vector里,清空線段樹。

2)求解重兒子,保留線段樹,暫時不加入重兒子vector里面的事件。

3)把vector里面所有的為插入線段樹的事件插入線段樹,求解答案。

然而我的代碼實現把清空和信息上傳放在了dfs的最后,為了代碼與題解統一,稍微改一下。

1)掃輕兒子。

2)掃重兒子。

3)把vector里面所有的為插入線段樹的事件插入線段樹,求解答案。

4)如果當前節點不是它父節點的重兒子,就清空線段樹,把vector上傳。

其實本質上是一樣的,個人感覺我這個挺好理解。

然而上面這個思路仍然不夠成熟,有一些具體實現會讓時間復雜度產生一些差別。

首先是如何求出重兒子。這個還是比較簡單的,dfs掃一遍。記得打完這個函數要調用!不要像某個姓吳的名字是兩個字的傻子一樣。

1 void pre_dfs(int p,int fa){
2     siz[p]=v[p].size();
3     for(int i=fir[p];i;i=l[i])if(to[i]!=fa){
4         pre_dfs(to[i],p),siz[p]+=siz[to[i]];
5         if(siz[to[i]]>siz[hson[p]])ihs[hson[p]]=0,hson[p]=to[i],ihs[to[i]]=1;
      //hson[p]存p的重兒子,ihs[p]表示p是否為父節點的重兒子
6 } 7 }

然后是怎么把vector里面的數據上傳。考慮極端情況:一條100000點的鏈,在最后一個點上有100000個事件。

顯然暴力一個一個事件地上傳會T成狗,這就是為什么要啟發式合並,這樣就有了nlog的合並復雜度的保證了。

1 void up(int p,int fa){
2     if(v[ref[p]].size()<v[ref[fa]].size()){//把小的往大的里面逐個插入
3         for(int i=0;i<v[ref[p]].size();++i)v[ref[fa]].push_back(v[ref[p]][i]);
4         v[ref[p]].clear();//清空,防止爆內存,但是好像不必要
5     }
6     else{for(int i=0;i<v[ref[fa]].size();++i)v[ref[p]].push_back(v[ref[fa]][i]);v[ref[fa]].clear();ref[fa]=ref[p];}
    //如果父節點的vector比兒子小,那么把父親節點的vector倒進子節點的之后,要把代表元素修改一下,fa的vector不再是v[fa]而是v[p]
7 }

再接下來需要實現的就是線段樹了。設t數組表示球的總數量,c數組表示有貢獻的球的數量(即每個顏色第一次出現的球的數量)

首先是修改,為了以后還要清空線段樹(具體的等會再說),需要可加可減,倒沒什么區別

1 void insert(int p,int pos,int tt,int cc){
2     if(cl[p]==cr[p]){t[p]+=tt;c[p]+=cc;return;}//已經到了線段樹葉子
3     if(pos<=cr[p<<1])insert(p<<1,pos,tt,cc);
4     else insert(p<<1|1,pos,tt,cc);
5     t[p]=t[p<<1]+t[p<<1|1];c[p]=c[p<<1]+c[p<<1|1];
6 }

 詢問倒是有一點變化,有人說是二分查找什么的,我倒感覺有點多余。還記得平衡樹上查第k大數的操作嗎?

1 int ask(int p,int tt){
2     if(tt>=t[p])return c[p];//如果給這個節點分配的可容納空間比總球數還大那么所有有貢獻的球都計入答案
3     return (tt>t[p<<1]?c[p<<1]+ask(p<<1|1,tt-t[p<<1]):ask(p<<1,tt));
    //如果左子樹的球數就比容積大,那就去左子樹,否則帶上左子樹的所有貢獻去右子樹
4 }

線段樹也完事了,但是怎么清空啊?如果memset顯然是在找死了。

線段樹上絕大多數的點還是沒有值的,怎么避開它們?

你插入過的地方一定是有值的,那么再把vector走一遍走到哪清到哪就好了。

這又引出了一個問題:清空時需要掃所有的時間,但其實你的重兒子並沒有上傳。

但是它又已經在線段樹里面了,你不能再次插入,怎么辦?

很簡單啊,在處理完父節點的其它事件之后求解答案完畢再上傳就好了啊。

再稍稍修改一下dfs框架。

1)掃輕兒子。

2)掃重兒子。

3)把vector里面所有的為插入線段樹的事件插入線段樹,求解答案。

4)把重兒子的信息上傳到自己身上。

5)如果當前節點不是它父節點的重兒子,就清空線段樹,把vector上傳。

還有,你怎么知道一個球有沒有貢獻?

開一個數組(沒離散化就是map),記錄這種顏色出現的最早時間,掃所有操作:

如果這個顏色沒出現過,那么插入這個球,球數+1,貢獻+1

如果這個顏色出現過,但是是在更晚的時間,那么在那個時間的貢獻-1(不再是最早的了),在這個時間的球數和貢獻+1

如果這個顏色已經再更早的時間出現過,那么球數+1但不加貢獻

清空同理,用剛才的那個數組對照每個操作就行。

dfs的具體實現也有了。

 1 void dfs(int p,int fa){
 2     for(int i=fir[p];i;i=l[i])if(to[i]!=fa&&to[i]!=hson[p])dfs(to[i],p);//先掃輕兒子
 3     if(hson[p])dfs(hson[p],p);//最后掃重兒子
 4     for(int i=0;i<v[ref[p]].size();++i){//處理所有不在樹里的事件
 5         int tim=v[ref[p]][i].first,col=v[ref[p]][i].second;
 6         if(!al[col])insert(1,tim,1,1),al[col]=tim;//前面的事件里沒有這個顏色,有球有貢獻,記錄最早的時間
 7         else if(al[col]>tim)insert(1,al[col],0,-1),insert(1,tim,1,1),al[col]=tim;
      //時間比先插入的事件更早,把那個事件的貢獻去除,新事件是有球有貢獻的,更新最早時間
8 else insert(1,tim,1,0);//出現晚了,只有球,沒有貢獻 9 } 10 ans[p]=ask(1,min(ct[p],t[1]));if(hson[p])up(hson[p],p);//統計答案,把重兒子的事件傳上來 11 if(!ihs[p]){//如果不是父節點的重兒子 12 for(int i=0;i<v[ref[p]].size();++i){//清空線段樹 13 int tim=v[ref[p]][i].first,col=v[ref[p]][i].second; 14 if(al[col]==tim)insert(1,tim,-1,-1),al[col]=0;else insert(1,tim,-1,0);
        //如果你就是這個顏色里出現最早的,去掉球和貢獻,並且重置出現的時間,如果不是最早的,只扔球,沒有貢獻
        //好像也可以直接置為0,但我懶得再打個函數了
15 } 16 up(p,fa);//把自己的信息上傳 17 } 18 }

整體做完,感覺並不是很難啊。而且這個思路貌似挺暴力的啊。。

但是就是想不到。。。略略略。。。

 1 #include<cstdio>
 2 #include<vector>
 3 #include<map>
 4 #include<iostream>
 5 using namespace std;
 6 vector<pair<int,int> >v[100005];
 7 map<int,int>ma;
 8 int al[100005],fir[100005],l[200005],to[200005],cnt,n,m,q,ct[100005];
 9 int hson[100005],ans[100005],k,siz[100005],ihs[100005],ref[100005];
10 int cl[400005],cr[400005],t[400005],c[400005];
11 void connect(int a,int b){
12     l[++cnt]=fir[a];fir[a]=cnt;to[cnt]=b;
13     l[++cnt]=fir[b];fir[b]=cnt;to[cnt]=a;
14 }
15 void pre_dfs(int p,int fa){
16     siz[p]=v[p].size()+1;
17     for(int i=fir[p];i;i=l[i])if(to[i]!=fa){
18         pre_dfs(to[i],p),siz[p]+=siz[to[i]];
19         if(siz[to[i]]>siz[hson[p]])ihs[hson[p]]=0,hson[p]=to[i],ihs[to[i]]=1;
20     }
21 }
22 void build(int p,int l,int r){
23     cl[p]=l;cr[p]=r;
24     if(l==r)return;
25     build(p<<1,l,l+r>>1);build(p<<1|1,(l+r>>1)+1,r);
26 }
27 void insert(int p,int pos,int tt,int cc){
28     if(cl[p]==cr[p]){t[p]+=tt;c[p]+=cc;return;}
29     if(pos<=cr[p<<1])insert(p<<1,pos,tt,cc);
30     else insert(p<<1|1,pos,tt,cc);
31     t[p]=t[p<<1]+t[p<<1|1];c[p]=c[p<<1]+c[p<<1|1];
32 }
33 int ask(int p,int tt){
34     if(tt==t[p])return c[p];
35     return (tt>t[p<<1]?c[p<<1]+ask(p<<1|1,tt-t[p<<1]):ask(p<<1,tt));
36 }
37 void up(int p,int fa){
38     if(v[ref[p]].size()<v[ref[fa]].size()){
39         for(int i=0;i<v[ref[p]].size();++i)v[ref[fa]].push_back(v[ref[p]][i]);
40         v[ref[p]].clear();
41     }
42     else{for(int i=0;i<v[ref[fa]].size();++i)v[ref[p]].push_back(v[ref[fa]][i]);v[ref[fa]].clear();ref[fa]=ref[p];}
43 }
44 void dfs(int p,int fa){
45     for(int i=fir[p];i;i=l[i])if(to[i]!=fa&&to[i]!=hson[p])dfs(to[i],p);
46     if(hson[p])dfs(hson[p],p);
47     for(int i=0;i<v[ref[p]].size();++i){
48         int tim=v[ref[p]][i].first,col=v[ref[p]][i].second;
49         if(!al[col])insert(1,tim,1,1),al[col]=tim;
50         else if(al[col]>tim)insert(1,al[col],0,-1),insert(1,tim,1,1),al[col]=tim;
51         else insert(1,tim,1,0);
52     }
53     ans[p]=ask(1,min(ct[p],t[1]));if(hson[p])up(hson[p],p);
54     if(!ihs[p]){
55         for(int i=0;i<v[ref[p]].size();++i){
56             int tim=v[ref[p]][i].first,col=v[ref[p]][i].second;
57             if(al[col]==tim)insert(1,tim,-1,-1),al[col]=0;else insert(1,tim,-1,0);
58         }
59         up(p,fa);
60     }
61 }
62 int main(){
63     scanf("%d",&n);ihs[1]=1;
64     for(int i=1,a,b;i<n;++i)scanf("%d%d",&a,&b),connect(a,b);
65     for(int i=1;i<=n;++i)scanf("%d",&ct[i]),ref[i]=i;
66     scanf("%d",&m);build(1,1,m);
67     for(int i=1,p,c;i<=m;++i){
68         scanf("%d%d",&p,&c);if(ma.find(c)==ma.end())ma[c]=++k;
69         v[p].push_back(make_pair(i,ma[c]));
70     }
71     pre_dfs(1,0);dfs(1,0);
72     scanf("%d",&q);
73     for(int i=1,a;i<=q;++i)scanf("%d",&a),printf("%d\n",ans[a]);
74 }
完整代碼

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM