树结构的分治算法有两种常见形式:
1:对点的分治.首先选取一个点R作为树根,再递归处理以R的儿子为根的子树.此方法的优点在于期望时间复杂度较小.
2:对边的分治.选取一条边L,将原树分成两个互不相交的树,递归处理.此方法的优点在于思考的复杂度较低.
详细见漆子超的论文.
例题1.
Tree
Time Limit:1000MS |
Memory Limit:30000K |
Total Submissions:8136 |
Accepted:2387 |
Description
Give a tree with n vertices,each edge has a length(positive integer less than 1001). Define dist(u,v)=The min distance between node u and v. Give an integer k,for every pair (u,v) of vertices is called valid if and only if dist(u,v) not exceed k. Write a program that will count how many pairs which are valid for a given tree.
Input
The input contains several test cases. The first line of each test case contains two integers n, k. (n<=10000) The following n-1 lines each contains three integers u,v,l, which means there is an edge between node u and v of length l. The last test case is followed by two zeros.
Output
For each test case output the answer on a single line.
Sample Input
5 4
1 2 3
1 3 1
1 4 2
3 5 1
0 0
Sample Output
8
Source
题意:给定一棵有N(N<=10000)个节点的树,树中的边有一个权值W(W<=1001),定义dist(i,j)为从节点i到节点j的路径上的边的权值之和,求满足dist(i,j)<=K的点对数目.
思路:对于一棵有根树而言,题目要求的点对之间的路径有两种可能,1.路径不过根节点.2.路径过根节点.第1种情况可以递归处理.针对于第2种情况,对每一个子孙节点记录两种属性,1.到根节点的距离(depth).2.属于根节点的哪个儿子的子孙(belong).可以分两部分来考虑,第一部分:根节点作为端点,O(N)即可求出.第二部分:根不是端点,此种情况下点对的数量为根节点所有子孙中depth之和小于K的对数减去depth之和小于K且belong值相同的点的对数.通过排序可以以Nlog(N)的时间解决.
为了减少搜索树的深度,每次递归时可以用O(N)的时间找出树的重心作为树根进行计算.
P.S.楼教主的男人八题真是太叼了,这道题竟然让我这个水货写了七八个小时才A掉...
#include<stdio.h> #include<stdlib.h> #include<string.h> #include<vector> #define GroupSize 10025 using namespace std; vector<int> G[GroupSize],E[GroupSize]; int Depth[GroupSize],Belong[GroupSize],hash[GroupSize],sum[GroupSize],maxv[GroupSize]; bool vis[GroupSize]; int N,K,Sts,T,Ans; int cmp0(const void * x,const void * y) { int Px=*(int *)x,Py=*(int *)y; if (Depth[Px]<Depth[Py]) return -1; else if (Depth[Px]==Depth[Py]) return 0; else return 1; } int cmp1(const void * x,const void * y) { int Px=*(int *)x,Py=*(int *)y; if (Belong[Px]<Belong[Py]) return -1; else if (Belong[Px]>Belong[Py]) return 1; else if (Depth[Px]<Depth[Py]) return -1; else if (Depth[Px]==Depth[Py]) return 0; else return 1; } void dfs(int Root,int father) { hash[++Sts]=Root; sum[Root]=1; maxv[Root]=0; for (int i=0;i<G[Root].size();i++) { int v=G[Root][i]; if (v==father || vis[v]) continue; dfs(v,Root); sum[Root]+=sum[v]; maxv[Root]=maxv[Root]>sum[v] ? maxv[Root]:sum[v]; } } int GetRoot(int Root,int father) { Sts=0; dfs(Root,father); int Cnt=sum[Root],Min=0x7FFFFFFF,Tr; for (int i=1;i<=Sts;i++) { int v=hash[i]; int tmp=maxv[v]>Cnt-sum[v] ? maxv[v]:Cnt-sum[v]; if (tmp<Min) { Min=tmp; Tr=v; } } return Tr; } void find(int Root,int father) { for (int i=0;i<G[Root].size();i++) { int v=G[Root][i]; if (v==father || vis[v]) continue; if (Depth[Root]+E[Root][i]<=K) { hash[++T]=v; Depth[v]=Depth[Root]+E[Root][i]; Belong[v]=Belong[Root]; find(v,Root); } } } void GetNear(int Root,int father) { T=0; hash[0]=Root; Depth[Root]=0; Belong[Root]=father; for (int i=0;i<G[Root].size();i++) { int v=G[Root][i]; if (v==father || E[Root][i]>K || vis[v]) continue; hash[++T]=v; Depth[v]=E[Root][i]; Belong[v]=v; find(v,Root); } } int CountAll() { int R=T,ans=0; for (int i=1;i<=T;i++) { while (Depth[hash[i]]+Depth[hash[R]]>K && R>=1) R--; ans+=R; if (R>=i) ans--; } ans/=2; for (int i=1;i<=T;i++) if (Depth[hash[i]]<=K) ans++; return ans; } int CountRepeat() { int L=1,R,Cur,ans=0; while (L<=T) { for (int i=L;i<=T;i++) if (i==T || Belong[hash[i]]!=Belong[hash[i+1]]) { Cur=R=i; break; } for (int i=L;i<=R;i++) { while (Depth[hash[i]]+Depth[hash[Cur]]>K && Cur>=L) Cur--; ans+=Cur-L+1; if (Cur>=i) ans--; } L=R+1; } return ans/2; } void solve(int Root,int father) { Root=GetRoot(Root,father); vis[Root]=true; GetNear(Root,father); qsort(&hash[1],T,sizeof(int),cmp0); Ans+=CountAll(); qsort(&hash[1],T,sizeof(int),cmp1); Ans-=CountRepeat(); for (int i=0;i<G[Root].size();i++) { int v=G[Root][i]; if (v==father || vis[v]) continue; solve(v,Root); } } int main() { while (scanf("%d%d",&N,&K)!=EOF) { if (N+K==0) return 0; for (int i=1;i<=N;i++) G[i].clear(); for (int i=1;i<=N;i++) E[i].clear(); for (int i=1;i<N;i++) { int x,y,c; scanf("%d%d%d",&x,&y,&c); G[x].push_back(y); G[y].push_back(x); E[x].push_back(c); E[y].push_back(c); } memset(vis,0,sizeof(vis)); Ans=0; solve(1,-1); printf("%d\n",Ans); } return 0; }
总结:在这道题中学到了两点,1.用邻接表存树(我以前一直用vector),2.如何求树的重心.
1.如何用邻接表存树
Struct node { Int tar,len; Node *next; }; Node tree[2*N]; Node * head[N];
Head[i]指向最后一条与节点i有关的边.
对于每个node,保存一条边:朝向tar节点,权值为len,next指向上一条与该节点相关联的边.
添边操作:
void AddEdge(int x, int y, int z) { tree[ptr].v = y; tree[ptr].w = z; tree[ptr].next = head[x],head[x] = &tree[ptr++]; }
2.如何求出树的重心.
见下一例题.
Balancing Act
Time Limit:1000MS |
Memory Limit:65536K |
Total Submissions:7719 |
Accepted:3149 |
Description
Consider a tree T with N (1 <= N <= 20,000) nodes numbered 1...N. Deleting any node from the tree yields a forest: a collection of one or more trees. Define the balance of a node to be the size of the largest tree in the forest T created by deleting that node from T. For example, consider the tree:
2----1----3
| | |
6 4 7
|
5 Deleting node 4 yields two trees whose member nodes are {5} and {1,2,3,6,7}. The larger of these two trees has five nodes, thus the balance of node 4 is five. Deleting node 1 yields a forest of three trees of equal size: {2,6}, {3,7}, and {4,5}. Each of these trees has two nodes, so the balance of node 1 is two. For each input tree, calculate the node that has the minimum balance. If multiple nodes have equal balance, output the one with the lowest number.
Input
The first line of input contains a single integer t (1 <= t <= 20), the number of test cases. The first line of each test case contains an integer N (1 <= N <= 20,000), the number of congruence. The next N-1 lines each contains two space-separated node numbers that are the endpoints of an edge in the tree. No edge will be listed twice, and all edges will be listed.
Output
For each test case, print a line containing two integers, the number of the node with minimum balance and the balance of that node.
Sample Input
1
7
2 6
1 2
1 4
4 5
3 7
3 1
Sample Output
1 2
Source
POJ Monthly--2004.05.15 IOI 2003 sample task
题意:对于一棵树,设删去某个节点后剩余所有树的节点最大值为V,找出使V最小化的节点,即树的重心.
思路:先随便以一个节点为根,将这棵树建立起来.对树中的每一个节点,记录两种属性:1.所有该节点的子孙数(sum),该节点最有出息的儿子的子孙数(maxv).显然对每一个节点V=max(N-sum,maxv).
#include<stdio.h> #include<vector> using namespace std; vector<int> G[20025]; int sum[20025],maxv[20025]; int N; void dfs(int R,int father) { sum[R]=1; maxv[R]=0; for (int i=0;i<G[R].size();i++) { int v=G[R][i]; if (v==father) continue; dfs(v,R); sum[R]+=sum[v]; maxv[R]=maxv[R]>sum[v] ? maxv[R]:sum[v]; } } int main() { int T; scanf("%d",&T); while (T--) { scanf("%d",&N); for (int i=1;i<=N;i++) G[i].clear(); for (int i=1;i<N;i++) { int x,y; scanf("%d%d",&x,&y); G[x].push_back(y); G[y].push_back(x); } dfs(1,-1); int Min=0x7FFFFFFF,R; for (int i=1;i<=N;i++) { int tmp=maxv[i]>N-sum[i] ? maxv[i]:N-sum[i]; if (tmp<Min) { Min=tmp; R=i; } } printf("%d %d\n",R,Min); } return 0; }