1. 程式人生 > >關於樹的簡單整理

關於樹的簡單整理

連通 etc return ... 一個點 scan print -1 解釋

整理一些樹的,基本的,簡單的一些知識。

先寫一下關於樹的定義,相關術語。

樹,父節點、子節點、子樹、祖先、兄弟、根節點、葉節點、直徑、路徑、重心、直徑、最近公共祖先、生成樹、dfs序,樹形dp等

1、最近公共祖先

一般用倍增求LCA(Least Common Ancestors)。

按照樸素的做法,就是深的點跳到同一高度,然後兩個點一齊往上跳。跳到同一位置。

這樣其實不慢,一般的樹,深度為logn,所以這個復雜度可以是logn。但如果樹成了一條鏈,那麽復雜度就是O(n)。

而倍增求LCA,和這個思想是一樣的,深的點跳到同一高度,然後兩個點一齊往上跳。不過它不是一個點一個點的跳,看下面。

對於任何的數字都可以分解成2的次冪相加的形式(7 = 2^2+2^1+2^0,10 = 2^3+2^1...),證明也很簡單,任何一個十進制都可以分解成二進制表示。

那麽對於跳的任何高度都可以用二進制表示(跳7個,7 = 2^2+2^1+2^0,跳10個10 = 2^3+2^1),那麽我們預處理出每個點往上跳2的次冪的點,所到達的點是誰就好了。

然後和上面一樣跳。

這樣明顯比上面的快。

貼一下代碼 luogu3379

技術分享
 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4
5 using namespace std; 6 const int N = 1000100; 7 8 struct Edge { 9 int v,nxt; 10 } e[N]; 11 int f[N][25]; 12 int deth[N]; 13 int head[N]; 14 int n,m,s,tot,a,b,d; 15 16 void add(int u,int v) { 17 tot++; 18 e[tot].v = v; 19 e[tot].nxt = head[u]; 20 head[u] = tot;
21 } 22 23 void dfs(int x) { 24 for (int i=head[x]; i; i=e[i].nxt) { 25 int v = e[i].v; 26 if(!deth[v]) 27 { 28 deth[v] = deth[x]+1; 29 f[v][0] = x; 30 dfs(v); 31 } 32 } 33 } 34 35 void init() { 36 for (int j=1; j<=20; ++j) 37 for (int i=1; i<=n; ++i) 38 f[i][j] = f[f[i][j-1]][j-1]; 39 } 40 41 int lca(int u,int v) { 42 if (deth[u] < deth[v]) swap(u,v); 43 /*for (int i=20; i>=0; --i) 44 { 45 if (deth[f[u][i]] >= deth[v]) 46 u = f[u][i]; 47 }*/ 48 d = deth[u]-deth[v]; 49 for (int i=0; i<=20; ++i) { 50 if ((1<<i) & d) 51 u = f[u][i]; 52 } 53 if (u==v) return u; 54 for (int i=20; i>=0; --i) { 55 if(f[u][i] != f[v][i]) { 56 u = f[u][i]; 57 v = f[v][i]; 58 } 59 } 60 return f[u][0]; 61 } 62 63 int main() { 64 scanf("%d%d%d",&n,&m,&s); 65 for (int i=1; i<n; ++i) { 66 scanf("%d%d",&a,&b); 67 add(a,b); 68 add(b,a); 69 } 70 deth[s] = 1; 71 f[s][0] = 0; 72 dfs(s); 73 init(); 74 for (int i=1; i<=m; ++i) { 75 scanf("%d%d",&a,&b); 76 printf("%d\n",lca(a,b)); 77 } 78 return 0; 79 }
View Code

2、生成樹

生成樹計數

最小/大生成樹

prime算法和kruskal 算法。一般用kruskal 吧。

kruskal 算法原理很簡單,以最小生成樹為例。求最大的權值盡量小和且構成一棵樹。

先按每條邊的權值大小排序,從小到大依次加入,並查集判斷是否成為連通圖了即可

代碼

技術分享
 1 #include<iostream>
 2 #include<cstdio>
 3 #include<algorithm>
 4 
 5 using namespace std;
 6 
 7 const int MAXM = 200100;
 8 const int MAXN = 100100; 
 9 
10 struct edge {
11     int a,b,c;
12 } e[MAXM];
13 int fa[MAXN];
14 
15 int find(int a) {
16     return fa[a]==a?a:fa[a]=find(fa[a]);
17 }
18 bool cmp(edge a,edge b) {
19     return a.c<b.c;
20 }
21 int main() {
22     int n,m,ans,cnt;
23     scanf("%d%d",&n,&m);
24     for(int i=1; i<=n; ++i) fa[i] = i;
25     for(int i=1; i<=m; ++i) {
26         int a,b,c;
27         scanf("%d%d%d",&a,&b,&c);
28         e[i].a=a;
29         e[i].b=b;
30         e[i].c=c;
31     }
32     sort(e+1,e+m+1,cmp);
33     for(int i=1; i<=m; ++i) {
34         int aa = find(e[i].a);
35         int bb = find(e[i].b);
36         if(aa!=bb) {
37             cnt++;
38             fa[aa] = bb;
39             ans += e[i].c;
40             if(cnt==(n-1))break;
41         }
42     }
43     if(cnt==(n-1)) cout<<ans;
44     else cout<<"-1";
45     return 0;
46 }
View Code

3、樹的直徑

兩次bfs(dfs),指定任意一點為根,搜索出距離這個根最遠的點a,然後搜距離a最遠的點b,ab之間的路徑為為樹的直徑。

代碼 原題(poj2631 Roads in the North)

技術分享
 1 #include<cstdio>
 2 #include<queue>
 3 #include<algorithm>
 4 #include<cstring>
 5 
 6 using namespace std;
 7 
 8 const int MAXN = 10010;
 9 
10 struct Edge{
11     int to,w,nxt;
12     Edge(){}
13     Edge(int x,int y,int z){to = x,w = y,nxt = z;}
14 }e[500100];
15 
16 int head[MAXN<<1],dis[MAXN];
17 int tot;
18 queue<int>q;
19 
20 int bfs(int x)
21 {
22     memset(dis,-1,sizeof(dis));
23     q.push(x);
24     dis[x] = 0;
25     int id,mx = 0;
26     
27     while (!q.empty())
28     {
29         int u = q.front();
30         q.pop();
31         for (int i=head[u]; i; i=e[i].nxt)
32         {
33             int v = e[i].to, w = e[i].w;
34             if (dis[v]==-1)    //雙向邊,只算一次 
35             {
36                 dis[v] = dis[u]+w;
37                 if (dis[v]>mx) mx = dis[v],id = v;
38                 q.push(v);
39             }
40         }
41     }
42     return id;
43 }
44 int main()
45 {
46     int u,v,w;
47     while (scanf("%d%d%d",&u,&v,&w)!=EOF)
48     {
49         e[++tot] = Edge(v,w,head[u]);
50         head[u] = tot;
51         e[++tot] = Edge(u,w,head[v]);
52         head[v] = tot;
53     }
54     printf("%d",dis[bfs(bfs(1))]);    
55     return 0;
56 }
View Code

4、樹的重心

樹的重心定義:樹中的一個點,刪掉這個點,使得剩下的樹所構成的森林中最大的子樹節點數最少。

樹的重心推論:

1.設樹上的一個點S,樹上其余所有點到S點的距離之和最小,那麽S就是重心。

2.樹的重心不唯一。

然後可以依靠定義來求樹的重心。

首先以任意一個點,進行dfs,dfs過程中統計以每個點為根的子樹中,一共含有多少節點。

然後 總節點數 減去 子樹的節點數 減去1(自己),就是它父親那邊點的數量。

做差之後取最小值。

代碼:poj3107

技術分享
 1 #include<cstdio>
 2 #include<algorithm>
 3 #include<cstring>
 4 
 5 using namespace std;
 6 
 7 const int MAXN = 50010;
 8 const int MAXM = 100010;
 9 
10 struct Edge{
11     int to,nxt;
12 }e[MAXM];
13 int head[MAXM],tot;
14 int son[MAXN];
15 int ans[MAXN],p,Ans = 1e9,n;
16 
17 inline int read() {
18     int x = 0,f = 1;char ch = getchar();
19     for (; ch<0||ch>9; ch = getchar())
20         if (ch==-) f = -1;
21     for (; ch>=0&&ch<=9; ch = getchar())
22         x = x*10+ch-0;
23     return x*f;
24 }
25 inline void init() {
26     memset(head,0,sizeof(head));
27     memset(son,0,sizeof(son));
28     tot = 0;
29 }
30 inline void add_edge(int u,int v) {
31     e[++tot].to = v,e[tot].nxt = head[u],head[u] = tot; 
32 }
33 void dfs(int u,int fa) {
34     int cnt = 0;
35     for (int i=head[u]; i; i=e[i].nxt) {
36         int v = e[i].to;
37         if (v==fa) continue;
38         dfs(v,u);
39         son[u] += son[v]+1;
40         cnt = max(cnt,son[v]+1);
41     }
42     cnt = max(cnt,n-son[u]-1);
43     if (cnt<Ans) {Ans = cnt,p = 0,ans[++p] = u;}
44     else if (cnt==Ans) {ans[++p] = u;}
45 }
46 int main() {
47     
48     while (scanf("%d",&n)!=EOF) {
49         init();
50         for (int u,v,i=1; i<n; ++i) {
51             u = read(),v = read();
52             add_edge(u,v),add_edge(v,u);
53         }
54         dfs(1,0);
55         sort(ans+1,ans+p+1);
56         for (int i=1; i<=p; ++i) 
57             printf("%d ",ans[i]);    
58         printf("\n");    
59     }    
60     return 0;
61 }
View Code

5、dfs序

關於這個的內容太多了。這裏之簡要的敘述一下好了。

dfs序就是按照dfs的順序所構成的一個序列,(從名字解釋的qwq)

dfs的性質:一棵子樹所在的位置處於一個連續區間中。

deth[x]為x的深度,L[x]為dfs序中x的起始位置,R[x]為dfs序中x子樹的結束位置

然後處理子樹上的問題成立處理區間的問題,在樹上也變成了在序列上,所以可以用線段樹或者樹狀數組維護了。

求dfs序的代碼

技術分享
 1 void dfs(int u,int fa) {
 2     deth[u] = deth[fa]+1;
 3     q[++tot] = u;
 4     L[u] = tot;
 5     for (int i=head[u]; i; i=e[i].nxt) {
 6         int v = e[i].to;
 7         if (v==fa) continue;
 8         dfs(v,u);
 9     }
10     R[u] = tot;
11 }
View Code

總結

這是一篇入門基礎級的,沒有更多的敘述。(以後再加以補充吧)

樹的問題還有很多,這些都是關於基礎的一些問題,更多的,感興趣可以深入的研究;

謝謝觀看

關於樹的簡單整理