hdu 5296 Annoying problem (LCA)
2015-10-06 16:54
267 查看
题目大意:
给一棵带权树,和一个集合S,初始为空。现有两种操作:
1、选择某个顶点加入S
2、将S中某个顶点去掉。
问:每次操作后,使得集合S中的点联通的最小边权和是多少。
一、思考问题的本质是什么?
要使边权和最小,也即每次增加的边权要求最小。
当添加第一个顶点的时候,增加的边权为0
添加第二个顶点的时候,增加的边权为该点到第一个顶点的路径长度。
添加第三个顶点的时候,增加的最小边权应当是该点到第一个顶点和第二个顶点形成的链的距离。(当然,若在链上,距离为0)
……
就是说,添加顶点u的时候,之前添加的顶点已经形成了若干条链(任意两个顶点构成一条)
此时要找的就是u到这些链的最短的距离是多少,也就是:在之前添加的顶点中(集合S中)找两个顶点x,y,使得u到x、y构成的链的距离最短。
二、如何来找这两个点?
观察一棵树中顶点的DFS序,发现有如下特点:
1、对于任意一个结点u,DFS序比它大的结点,由u的子孙和u的右侧的所有节点(不含根节点)构成。
2、对于任意一个结点u,DFS序比它小的结点,由u到根节点经过的所有结点(不含u本身,其实就是u的所有祖先啦)和u的左侧的所有节点构成。
这也就意味着,可以通过DFS序,来确定树中两点的相对位置。
对于添加的点u:
1、若集合S中所有点的DFS序都比它小,那么集合中所有点都位于u的左侧或者是u的某个祖先结点。若存在某个点是u的祖先,为了使得增加的距离最少,那么找距离u最近的那个,即DFS序最大的那个,设为x,再任意选一个顶点y,求u到x-y链的距离即可;若均位于u的左侧,此时又有两种情况,一种是所有顶点都在一棵子树中,此时找DFS序最小的那个点(因为最靠近u)作为x,再任选一个作为y,再一种是顶点分散在u的左侧的不同子树中,那么选取DFS序最小的和最大的作为x、y(最小的和最大的必然位于不同子树中),求u到链x-y的距离即可。综上,为了统一方便,分别找集合S中DFS序最小和最大的两个点作为x、y即可。
2、若集合S中所有点的DFS序都比它大,那么集合中所有点都位于u的右侧或者是u的子孙,同上分情况考虑,可以得到:也是找集合S中DFS序最小和最大的两个点作为x、y。
3、若集合S中的点的DFS序有比u大的,也有比u小的,此时分如下几种情况:
(1)比顶点u的DFS序大的点都在u的右侧,比顶点u的DFS序小的点都在u的左侧。此时任意选取左侧一个点和右侧一个点作为x、y即可。
(2)存在u的子孙在集合S中,且比顶点u的DFS序小的点都在u的左侧。容易知道,此时无需再增加边。即选取的时候只需保证链x-y经过u即可。
(3)比顶点u的DFS序大的点都在u的右侧,且存在u的祖先结点在S中。此时选取u的祖先结点DFS序最大的那个(也就是DFS序比u小的最大的那个),然后在右侧随便选一个即可。
(4)存在u的子孙在集合S中,且存在u的祖先结点在S中,显然此时也无需再增加边,选取的时候保证链x-y经过u即可。
综上,为了统一方便,分别找集合S中DFS序比u小的最大的那个、比u大的任意一个即可。
三、找到了之后如何求点到链的距离?
设dis[u]表示顶点u到根节点的距离
那么u点 到x点 距离表示为 dis[u]+dis[x]-2dis[lca(x,u)]
那么u点 到y点 距离表示为 dis[u]+dis[y]-2dis[lca(y,u)]
上面的距离的和减掉 x到y的距离dis[x]+dis[y]-2dis[lca(y,x)]
减去之后,发现得到的结果刚好是u到链x-y距离的2倍,除以2就是所求的结果了。
给一棵带权树,和一个集合S,初始为空。现有两种操作:
1、选择某个顶点加入S
2、将S中某个顶点去掉。
问:每次操作后,使得集合S中的点联通的最小边权和是多少。
一、思考问题的本质是什么?
要使边权和最小,也即每次增加的边权要求最小。
当添加第一个顶点的时候,增加的边权为0
添加第二个顶点的时候,增加的边权为该点到第一个顶点的路径长度。
添加第三个顶点的时候,增加的最小边权应当是该点到第一个顶点和第二个顶点形成的链的距离。(当然,若在链上,距离为0)
……
就是说,添加顶点u的时候,之前添加的顶点已经形成了若干条链(任意两个顶点构成一条)
此时要找的就是u到这些链的最短的距离是多少,也就是:在之前添加的顶点中(集合S中)找两个顶点x,y,使得u到x、y构成的链的距离最短。
二、如何来找这两个点?
观察一棵树中顶点的DFS序,发现有如下特点:
1、对于任意一个结点u,DFS序比它大的结点,由u的子孙和u的右侧的所有节点(不含根节点)构成。
2、对于任意一个结点u,DFS序比它小的结点,由u到根节点经过的所有结点(不含u本身,其实就是u的所有祖先啦)和u的左侧的所有节点构成。
这也就意味着,可以通过DFS序,来确定树中两点的相对位置。
对于添加的点u:
1、若集合S中所有点的DFS序都比它小,那么集合中所有点都位于u的左侧或者是u的某个祖先结点。若存在某个点是u的祖先,为了使得增加的距离最少,那么找距离u最近的那个,即DFS序最大的那个,设为x,再任意选一个顶点y,求u到x-y链的距离即可;若均位于u的左侧,此时又有两种情况,一种是所有顶点都在一棵子树中,此时找DFS序最小的那个点(因为最靠近u)作为x,再任选一个作为y,再一种是顶点分散在u的左侧的不同子树中,那么选取DFS序最小的和最大的作为x、y(最小的和最大的必然位于不同子树中),求u到链x-y的距离即可。综上,为了统一方便,分别找集合S中DFS序最小和最大的两个点作为x、y即可。
2、若集合S中所有点的DFS序都比它大,那么集合中所有点都位于u的右侧或者是u的子孙,同上分情况考虑,可以得到:也是找集合S中DFS序最小和最大的两个点作为x、y。
3、若集合S中的点的DFS序有比u大的,也有比u小的,此时分如下几种情况:
(1)比顶点u的DFS序大的点都在u的右侧,比顶点u的DFS序小的点都在u的左侧。此时任意选取左侧一个点和右侧一个点作为x、y即可。
(2)存在u的子孙在集合S中,且比顶点u的DFS序小的点都在u的左侧。容易知道,此时无需再增加边。即选取的时候只需保证链x-y经过u即可。
(3)比顶点u的DFS序大的点都在u的右侧,且存在u的祖先结点在S中。此时选取u的祖先结点DFS序最大的那个(也就是DFS序比u小的最大的那个),然后在右侧随便选一个即可。
(4)存在u的子孙在集合S中,且存在u的祖先结点在S中,显然此时也无需再增加边,选取的时候保证链x-y经过u即可。
综上,为了统一方便,分别找集合S中DFS序比u小的最大的那个、比u大的任意一个即可。
三、找到了之后如何求点到链的距离?
设dis[u]表示顶点u到根节点的距离
那么u点 到x点 距离表示为 dis[u]+dis[x]-2dis[lca(x,u)]
那么u点 到y点 距离表示为 dis[u]+dis[y]-2dis[lca(y,u)]
上面的距离的和减掉 x到y的距离dis[x]+dis[y]-2dis[lca(y,x)]
减去之后,发现得到的结果刚好是u到链x-y距离的2倍,除以2就是所求的结果了。
#include<iostream> #include<cstdio> #include<algorithm> #include<cstring> #include<stack> #include<cmath> #include<set> #include<queue> #include<vector> using namespace std; #define maxn 100001 #define maxq 70001 struct Edge{ int to,next,w; }edge[maxn<<1]; int dis[maxn],head[maxn],stk[maxn<<1],dep[maxn<<1],cnt,pos[maxn],E[maxn<<1],DFN[maxn],dfn,f[maxn<<1][20],dfn2,D[maxn]; bool vis[maxn]; inline void add(int u,int v,int w) { edge[cnt].to=v; edge[cnt].w=w; edge[cnt].next=head[u]; head[u]=cnt++; } void init() { memset(head,-1,sizeof(head)); memset(pos,-1,sizeof(pos)); memset(vis,0,sizeof(vis)); cnt=dfn=dfn2=0; } void dfs(int u,int deep) { if(pos[u]!=-1) return; DFN[dfn2]=u,D[u]=dfn2++,E[dfn]=u,dep[dfn]=deep,pos[u]=dfn++; for(int i=head[u];~i;i=edge[i].next) { int v=edge[i].to; if(pos[v]==-1) { dis[v]=dis[u]+edge[i].w; dfs(v,deep+1); E[dfn]=u,dep[dfn++]=deep; } } } void init_RMQ(int n) { for(int i=1;i<=n;++i) f[i][0]=i; for(int j=1;(1<<j)<=n;++j) for(int i=1;i+(1<<j)-1<=n;++i) { if(dep[f[i][j-1]]<dep[f[i+(1<<(j-1))][j-1]]) f[i][j]=f[i][j-1]; else f[i][j]=f[i+(1<<(j-1))][j-1]; } } inline int RMQ(int L,int R) { int k=0; while(1<<(k+1)<=R-L+1) ++k; if(dep[f[L][k]]<dep[f[R-(1<<k)+1][k]]) return f[L][k]; return f[R-(1<<k)+1][k]; } inline int lca(int u,int v) { if(pos[u]>pos[v]) return E[RMQ(pos[v],pos[u])]; return E[RMQ(pos[u],pos[v])]; } set<int> S; set<int>::iterator it; int cal(int u) { int x,y; if(S.size()<1) return 0; it=S.lower_bound(D[u]); if(it==S.begin()||it==S.end()) { x=DFN[*S.begin()]; y=DFN[*S.rbegin()]; } else{ x=DFN[*it]; --it; y=DFN[*it]; } return dis[u]+dis[lca(x,y)]-dis[lca(x,u)]-dis[lca(y,u)]; } int main() { int T,n,i,u,v,w,q,k; scanf("%d",&T); for(int ca=1;ca<=T;++ca) { scanf("%d%d",&n,&q); init(); S.clear(); for(i=1;i<n;++i) { scanf("%d%d%d",&u,&v,&w); add(u,v,w); add(v,u,w); } dis[1]=0; dfs(1,0); init_RMQ(2*n-1); printf("Case #%d:\n",ca); int ans=0; while(q--){ scanf("%d%d",&k,&u); if(k==1) { if(!vis[u]) { vis[u]=1; ans+=cal(u); S.insert(D[u]); } } else { if(vis[u]) { vis[u]=0; S.erase(D[u]); ans-=cal(u); } } printf("%d\n",ans); } } return 0; }
相关文章推荐
- Android中铃声总结【安卓源码解析一】
- AE实现空间分析
- HRESULT用法
- 沉浸式状态栏
- jquery判断checkbox是否选中及改变checkbox状态
- [Python进阶-7]文件和目录的IO操作,以及json序列化和反序列化
- 【计蒜客】难题题库 001 A+B+C问题
- 五校联考六T2
- yii2 - 邮件发送-示例
- C++输入输出流实现未知数目的输入(相当于实现python的split函数)
- mac开发android之环境搭建--JDK
- AngularJS 指令之 ng-style
- android下的名词/片段解释
- SAP系统常用配置参数设定
- Linux内核模块指南(第九章===>第十章完)。。。翻译完。。。
- Qt学习交流(广告)
- Spineer的用法
- UVa 10340 - All in All【水】
- 五校联考六T1
- 解决虚拟机克隆CentOS系统后eth0消失,显示eth1的问题