LCA(倍增在线算法) codevs 2370 小机房的树
2016-05-17 23:24
267 查看
codevs 2370 小机房的树
时间限制: 1 s空间限制: 256000 KB
题目等级 : 钻石 Diamond
题目描述 Description
小机房有棵焕狗种的树,树上有N个节点,节点标号为0到N-1,有两只虫子名叫飘狗和大吉狗,分居在两个不同的节点上。有一天,他们想爬到一个节点上去搞基,但是作为两只虫子,他们不想花费太多精力。已知从某个节点爬到其父亲节点要花费 c 的能量(从父亲节点爬到此节点也相同),他们想找出一条花费精力最短的路,以使得搞基的时候精力旺盛,他们找到你要你设计一个程序来找到这条路,要求你告诉他们最少需要花费多少精力
输入描述 Input Description
第一行一个n,接下来n-1行每一行有三个整数u,v, c 。表示节点 u 爬到节点 v 需要花费 c 的精力。 第n+1行有一个整数m表示有m次询问。接下来m行每一行有两个整数 u ,v 表示两只虫子所在的节点
输出描述 Output Description
一共有m行,每一行一个整数,表示对于该次询问所得出的最短距离。
样例输入 Sample Input
3
1 0 1
2 0 1
3
1 0
2 0
1 2
样例输出 Sample Output
1
1
2
数据范围及提示 Data Size & Hint
1<=n<=50000, 1<=m<=75000, 0<=c<=1000
分类标签 Tags 点此展开
最近公共祖先 图论资料来自:http://www.tuicool.com/articles/N7jQV32 http://www.cnblogs.com/wuminye/p/3532397.html
最近公共祖先 LCA 倍增法
【简介】解决LCA问题的倍增法是一种基于倍增思想的在线算法。
【原理】
原理和同样是使用倍增思想的RMQ-ST 算法类似,比较简单,想清楚后很容易实现。
对于每个节点u , ancestors[u][k] 表示 u 的第2k个祖先是谁。很容易就想到递推式: ancestors[j][i] = ancestors[ancestors[j][i - 1]][i - 1]; 根据二进制原理,理论上 u 的所有祖先都可以根据ancestors数组多次跳转得到,这样就间接地记录了每个节点的祖先信息。
查询LCA(u,v)的时候:
(一)u和v所在的树的层数如果一样,令u'=u。否则需要平衡操作(假设u更深),先找到u的一个祖先u', 使得u'的层数和v一样,此时LCA(u,v)=LCA(u',v) 。证明很简单:如果LCA(u,v)=v , 那么u'一定等于v ;如果LCA(u,v)=k ,k!=v ,那么k 的深度一定小于 v , u、u'、v 一定在k的子树中;综上所述,LCA(u,v)=LCA(u',v)一定成立。
(二)此时u' 和 v 的祖先序列中一开始的部分一定有所重叠,重叠部分的最后一个元素(也就是深度最深,与u'、v最近的元素)就是所求的LCA(u,v)。这里ancestors数组就可以派上用场了。找到第一个不重叠的节点k,LCA(u,v)=ancestors[k][0] 。 找k的过程利用二进制贪心思想,先尽可能跳到最上层的祖先,如果两祖先相等,说明完全可以跳小点,跳的距离除2,这样一步步跳下去一定可以找到k。
1. DFS预处理出所有节点的深度和父节点 inline void dfs(int u) { int i; for(i=head[u];i!=-1;i=next[i]) { if (!deep[to[i]]) { deep[to[i]] = deep[u]+1; p[to[i]][0] = u; //p[x][0]保存x的父节点为u; dfs(to[i]); } } } 2. 初始各个点的2^j祖先是谁 ,其中 2^j (j =0...log(该点深度))倍祖先,1倍祖先就是父亲,2倍祖先是父亲的父亲......。 void init() { int i,j; //p[i][j]表示i结点的第2^j祖先 for(j=1;(1<<j)<=n;j++) for(i=1;i<=n;i++) if(p[i][j-1]!=-1) p[i][j]=p[p[i][j-1]][j-1];//i的第2^j祖先就是i的第2^(j-1)祖先的第2^(j-1)祖先 } 3.从深度大的节点上升至深度小的节点同层,如果此时两节点相同直接返回此节点,即lca。 否则,利用倍增法找到最小深度的 p[a][j]!=p[b][j],此时他们的父亲p[a][0]即lca。 int lca(int a,int b)//最近公共祖先 { int i,j; if(deep[a]<deep[b])swap(a,b); for(i=0;(1<<i)<=deep[a];i++); i--; //使a,b两点的深度相同 for(j=i;j>=0;j--) if(deep[a]-(1<<j)>=deep[b]) a=p[a][j]; if(a==b)return a; //倍增法,每次向上进深度2^j,找到最近公共祖先的子结点 for(j=i;j>=0;j--) { if(p[a][j]!=-1&&p[a][j]!=p[b][j]) { a=p[a][j]; b=p[b][j]; } } return p[a][0]; }
附上题解:
#define N 50100 #include<iostream> using namespace std; #include<cstdio> #include<cstring> #define L 17 struct Edge{ int v,last,c; }edge[N*6]; int head ,p [L]; int deep ={0}; int root ={0}; long long dis ={0}; int n,m,u,v,c,t=0; void add_edge(int u,int v,int w) { ++t; edge[t].v=v;/*建边*/ edge[t].c=w; edge[t].last=head[u]; head[u]=t; //root[u]++; } void input() { scanf("%d",&n); for(int i=1;i<n;++i) { scanf("%d%d%d",&u,&v,&c); add_edge(u,v,c); add_edge(v,u,c); } memset(p,-1,sizeof(p));/*因为节点编号是从0开始的,所以把祖先不存在,设为-1*/ } void dfs(int u,long long di) { dis[u]=di;/*统计u到根节点的距离*/ for(int l=head[u];l;l=edge[l].last) { if(!deep[edge[l].v]) { deep[edge[l].v]=deep[u]+1;/*处理孩子的深度*/ p[edge[l].v][0]=u;/*初始化p数组*/ dfs(edge[l].v,di+edge[l].c); } } } void init() { int i,j; for(j=1;(1<<j)<n;j++) for(int i=0;i<n;++i) if(p[i][j]=-1) p[i][j]=p[p[i][j-1]][j-1];/*DP处理出i的所有2^j祖先是谁*/ } int lca(int a,int b)/*求最近公共祖先*/ { int i,j; if(deep[a]<deep[b]) swap(a,b); for(i=0;(1<<i)<=deep[a];++i); i--;/*i为估计a到根节点的最远距离,下边的平衡操作,跳点从i开始,一定可以实现*/ for(j=i;j>=0;--j) if(deep[a]-deep[b]>=(1<<j))/*倍增缩短a与b之间的距离*/ a=p[a][j]; if(a==b) return a;/*当a和b到了同一深度的时候,判断是否已经相同了*/ for(int j=i;j>=0;--j) { if(p[a][j]!=-1&&p[a][j]!=p[b][j]) { a=p[a][j];/*最终的a是lca的子节点*/ b=p[b][j]; } }/*先大步大步的蹦,每蹦一步,路程减少,下次蹦前一次的一半,直到蹦不了了,就是答案*/ return p[a][0]; } /*当a有祖先,并且a,b的祖先不相同的时候,(我们想要寻找的就是lca的子节点,也就是最小深度的p[a][j]!=p[b][j]),根据二进制原理,一定可以通过各种组合走到每一个祖先*/ int main() { input(); dfs(0,0);/*题目意思是0为根节点*/ /*for(int i=0;i<n;++i) { if(root[i]==2) { dfs(i,0);/*如果是一棵二叉树,可以统计出度为2的点是根节点*/ break; } }*/ init(); scanf("%d",&m); while(m--) { scanf("%d%d",&u,&v); int zu=lca(u,v);/*在线算法,可以按照顺序查询*/ cout<<dis[u]+dis[v]-2*dis[zu]<<endl;/*求最近距离的公式*/ } return 0; }
相关文章推荐
- PySide——Python图形化界面
- Improving the quality of the output
- ●获取汉字全拼
- 波特率跟比特率或者传码率跟传信率的区别
- Python 代码性能优化
- Linux Eclipse C/C++ 报错 launch failed
- 安卓WebView的那些坑
- linux磁盘分区fdisk分区和parted分区
- python匹配ip正则
- HuaXinIM聊项目阶段总结四(在线用户列表与聊天窗口的实现)
- 产品经理需要这样几个好习惯
- HDOJ1800(哈希)
- 软件工程练习——找水王
- ●获取汉字首拼
- 线程 implements Runnable & 线程睡眠
- C语言指针 和 学生宿舍管理系统
- Network Panel说明
- [置顶] 【卷积神经网络-进化史】从LeNet到AlexNet
- C语言中预定义符 __FILE__, __LINE__, __FUNCTION__, __DATE__, __TIME__ 的使用
- 【Android测试】【第十六节】Instrumentation——初识+实战