洛谷2542 [AHOI2005]航线规划 BZOJ1969
2017-06-09 15:24
429 查看
题目描述
对Samuel星球的探险已经取得了非常巨大的成就,于是科学家们将目光投向了Samuel星球所在的星系——一个巨大的由千百万星球构成的Samuel星系。
星际空间站的Samuel II巨型计算机经过长期探测,已经锁定了Samuel星系中许多星球的空间坐标,并对这些星球从1开始编号1、2、3……。
一些先遣飞船已经出发,在星球之间开辟探险航线。
探险航线是双向的,例如从1号星球到3号星球开辟探险航线,那么从3号星球到1号星球也可以使用这条航线。
例如下图所示:
在5个星球之间,有5条探险航线。
A、B两星球之间,如果某条航线不存在,就无法从A星球抵达B星球,我们则称这条航线为关键航线。
显然上图中,1号与5号星球之间的关键航线有1条:即为4-5航线。
然而,在宇宙中一些未知的磁暴和行星的冲撞,使得已有的某些航线被破坏,随着越来越多的航线被破坏,探险飞船又不能及时回复这些航线,可见两个星球之间的关键航线会越来越多。
假设在上图中,航线4-2(从4号星球到2号星球)被破坏。此时,1号与5号星球之间的关键航线就有3条:1-3,3-4,4-5。
小联的任务是,不断关注航线被破坏的情况,并随时给出两个星球之间的关键航线数目。现在请你帮助完成。
输入输出格式
输入格式:
第一行有两个整数N,M。表示有N个星球(1< N < 30000),初始时已经有M条航线(1 < M < 100000)。随后有M行,每行有两个不相同的整数A、B表示在星球A与B之间存在一条航线。接下来每行有三个整数C、A、B。C为1表示询问当前星球A和星球B之间有多少条关键航线;C为0表示在星球A和星球B之间的航线被破坏,当后面再遇到C为1的情况时,表示询问航线被破坏后,关键路径的情况,且航线破坏后不可恢复; C为-1表示输入文件结束,这时该行没有A,B的值。被破坏的航线数目与询问的次数总和不超过40000。
输出格式:
对每个C为1的询问,输出一行一个整数表示关键航线数目。
输入输出样例
输入样例#1:
5 5
1 2
1 3
3 4
4 5
4 2
1 1 5
0 4 2
1 5 1
-1
输出样例#1:
1
3
说明
我们保证无论航线如何被破坏,任意时刻任意两个星球都能够相互到达。在整个数据中,任意两个星球之间最多只可能存在一条直接的航线。
思路:我们先考虑如果没有删除边的操作,只有询问怎么做。 我们可以先求出边-双连通分量。那么整个图就是一颗树了。其中树边就是题目所说的“关键边”。那么u到v的路径的关键边数量相当于就是树上u到v的距离,那么首先知道u,v的lca,然后我们如果事先求出了树上每个点的深度,那么u到v的距离就是deep(u)-deep(lca)+deep(v)-deep(lca) 。 怎么快速求出两个点的lca呢? 我们可以用rmq 来求,rmq预处理是O(nlogn) ,查询时O(1)….我们要怎么查询呢?
我们看一下这棵树:
1
/ \
2 3
/ | \ |
4 5 6 7
那么我们从根开始dfs ,那么我们得到的时间序是: 1(1),2(2),4(3),2(2),5(4),2(2),6(5),2(2),1(1),3(6),7(7),3(6),1(1)
其中括号中的表示某个点第一次遇到的时间,括号外的就是点的编号。发现是么没有,如果我们询问的是4和5的lca,那么lca应该在…..4(3),2(2),5(4) 之中,然而,其中2是最早遇到的,所以lca就是2,再看2和7,那么lca应该在2(2),4(3),2(2),5(4),2(2),6(5),2(2),1(1),3(6),7(7)之中其中时间最早的是1,所以他们lca就是1。对于更一般的情况,我们时间序列的形式是怎么样的,我们设根是rt,他的时间是1,那么时间序的形式如下rt(1) -> (左边第一颗子树)->rt(1)->(左边第二颗子树)->rt(1)->……(右边第一颗子树)->rt(1), 注意这个形式是递归的,那么如果第一点在第一颗子树,第二个点在第二颗子树,那么他们之间的所有时间序中时间最小的是rt,rt就是他们的lca,如果两个点在同一颗子树,只需要要看那一颗子树的那段时间序,直到两个点在两颗子树中,这时候那段序列里面时间最小的点就是他们的lca。不懂没关系,画几个图,把时间序列写出来,按照我说的模拟一下求lca的过程,就知道了。
好!这时候我们已经知道要怎么求lca了,就是u在时间序中的起始位置和v在时间序中起始位置之间时间最小的点。我们就可以用rmq来统计某个区间上的时间的最小值了。求出了lca,两个点的距离就出来了。
到这里为止,我们已经知道如何快速求出一个图任意两点之间的“关键边”,但可惜的是,题目中有删除边的操作。那要怎么破?我们要关心的量是lca和每个点的深度,看下我们应该如何维护这两个量。
我们把所有的操作反过来做,就是从经过所有这些操作后剩下的图开始,然后从后往前的操作,那么之前的删边,相当于就是加一条边了,这样看起来会好做一点。我们不妨设最后的图是一颗树(如果不是的话,我们可以在原来的操作里面再多加一些删除边的操作,让最后的图变成一棵树),那么当我们要加一条边进这颗树的时候,这棵树必定会出现一个环,那么这个环上的所有的树边都应该要删除,删除的操作可以用并查集把环上点并到一起。 那么LCA其实并没有怎么改变,只是从原来的LCA(u,v)变成了find(LCA(find(u),find(v)) 其中find(x) 是找到并查集中u的根。 很好!第一个lca的维护已经解决了。
接下来看如何维护一个点的深度。假设我们删掉一条边(u,fa(u)) fa(u) 表示的是u的父亲节点。 那么u节点对应的子树里面的所有节点的深度都需要减1,这时候我们的时间戳又起作用了,我们可以在dfs的时候记录每个点对应的最早时间L(就是根的时间)和他的子孙中最大的时间R(就是最有边的叶子的时间),不难想到一个节点的所有子孙的时间值能够涵盖[L+1,R] 中的每一个整数。所以我们可以用线段树来维护一颗子树中的深度总和。每删掉一条边(u,fa(u))就是将L[u],R[u]中的值全部-1。这样就实现了一个节点所有子孙的深度都-1的效果。
这道题目说到这里基本上就已经解出来了。剩下就是考验你码力的时候了。然后的话由于点有30000,递归最多要到30000层,所以直接递归的dfs可能会溢出,所以我用的是栈模拟递归。虽然是这么说,但是别人的用递归的代码还是过了Orz。。。。
最后附上三个链接:
http://blog.sina.com.cn/s/blog_51cea4040100gjwg.html
http://blog.sina.com.cn/s/blog_51cea4040100gjxe.html
http://blog.csdn.net/wsx1754175/article/details/22519417
这两个是这道题目的题解。如果我讲得不够清楚,可以结合这两个里面的看,相信你们一定能搞懂这道题目的!而且里面有一个很不错的线段树的写法,大家可以参考一下哦
对Samuel星球的探险已经取得了非常巨大的成就,于是科学家们将目光投向了Samuel星球所在的星系——一个巨大的由千百万星球构成的Samuel星系。
星际空间站的Samuel II巨型计算机经过长期探测,已经锁定了Samuel星系中许多星球的空间坐标,并对这些星球从1开始编号1、2、3……。
一些先遣飞船已经出发,在星球之间开辟探险航线。
探险航线是双向的,例如从1号星球到3号星球开辟探险航线,那么从3号星球到1号星球也可以使用这条航线。
例如下图所示:
在5个星球之间,有5条探险航线。
A、B两星球之间,如果某条航线不存在,就无法从A星球抵达B星球,我们则称这条航线为关键航线。
显然上图中,1号与5号星球之间的关键航线有1条:即为4-5航线。
然而,在宇宙中一些未知的磁暴和行星的冲撞,使得已有的某些航线被破坏,随着越来越多的航线被破坏,探险飞船又不能及时回复这些航线,可见两个星球之间的关键航线会越来越多。
假设在上图中,航线4-2(从4号星球到2号星球)被破坏。此时,1号与5号星球之间的关键航线就有3条:1-3,3-4,4-5。
小联的任务是,不断关注航线被破坏的情况,并随时给出两个星球之间的关键航线数目。现在请你帮助完成。
输入输出格式
输入格式:
第一行有两个整数N,M。表示有N个星球(1< N < 30000),初始时已经有M条航线(1 < M < 100000)。随后有M行,每行有两个不相同的整数A、B表示在星球A与B之间存在一条航线。接下来每行有三个整数C、A、B。C为1表示询问当前星球A和星球B之间有多少条关键航线;C为0表示在星球A和星球B之间的航线被破坏,当后面再遇到C为1的情况时,表示询问航线被破坏后,关键路径的情况,且航线破坏后不可恢复; C为-1表示输入文件结束,这时该行没有A,B的值。被破坏的航线数目与询问的次数总和不超过40000。
输出格式:
对每个C为1的询问,输出一行一个整数表示关键航线数目。
输入输出样例
输入样例#1:
5 5
1 2
1 3
3 4
4 5
4 2
1 1 5
0 4 2
1 5 1
-1
输出样例#1:
1
3
说明
我们保证无论航线如何被破坏,任意时刻任意两个星球都能够相互到达。在整个数据中,任意两个星球之间最多只可能存在一条直接的航线。
思路:我们先考虑如果没有删除边的操作,只有询问怎么做。 我们可以先求出边-双连通分量。那么整个图就是一颗树了。其中树边就是题目所说的“关键边”。那么u到v的路径的关键边数量相当于就是树上u到v的距离,那么首先知道u,v的lca,然后我们如果事先求出了树上每个点的深度,那么u到v的距离就是deep(u)-deep(lca)+deep(v)-deep(lca) 。 怎么快速求出两个点的lca呢? 我们可以用rmq 来求,rmq预处理是O(nlogn) ,查询时O(1)….我们要怎么查询呢?
我们看一下这棵树:
1
/ \
2 3
/ | \ |
4 5 6 7
那么我们从根开始dfs ,那么我们得到的时间序是: 1(1),2(2),4(3),2(2),5(4),2(2),6(5),2(2),1(1),3(6),7(7),3(6),1(1)
其中括号中的表示某个点第一次遇到的时间,括号外的就是点的编号。发现是么没有,如果我们询问的是4和5的lca,那么lca应该在…..4(3),2(2),5(4) 之中,然而,其中2是最早遇到的,所以lca就是2,再看2和7,那么lca应该在2(2),4(3),2(2),5(4),2(2),6(5),2(2),1(1),3(6),7(7)之中其中时间最早的是1,所以他们lca就是1。对于更一般的情况,我们时间序列的形式是怎么样的,我们设根是rt,他的时间是1,那么时间序的形式如下rt(1) -> (左边第一颗子树)->rt(1)->(左边第二颗子树)->rt(1)->……(右边第一颗子树)->rt(1), 注意这个形式是递归的,那么如果第一点在第一颗子树,第二个点在第二颗子树,那么他们之间的所有时间序中时间最小的是rt,rt就是他们的lca,如果两个点在同一颗子树,只需要要看那一颗子树的那段时间序,直到两个点在两颗子树中,这时候那段序列里面时间最小的点就是他们的lca。不懂没关系,画几个图,把时间序列写出来,按照我说的模拟一下求lca的过程,就知道了。
好!这时候我们已经知道要怎么求lca了,就是u在时间序中的起始位置和v在时间序中起始位置之间时间最小的点。我们就可以用rmq来统计某个区间上的时间的最小值了。求出了lca,两个点的距离就出来了。
到这里为止,我们已经知道如何快速求出一个图任意两点之间的“关键边”,但可惜的是,题目中有删除边的操作。那要怎么破?我们要关心的量是lca和每个点的深度,看下我们应该如何维护这两个量。
我们把所有的操作反过来做,就是从经过所有这些操作后剩下的图开始,然后从后往前的操作,那么之前的删边,相当于就是加一条边了,这样看起来会好做一点。我们不妨设最后的图是一颗树(如果不是的话,我们可以在原来的操作里面再多加一些删除边的操作,让最后的图变成一棵树),那么当我们要加一条边进这颗树的时候,这棵树必定会出现一个环,那么这个环上的所有的树边都应该要删除,删除的操作可以用并查集把环上点并到一起。 那么LCA其实并没有怎么改变,只是从原来的LCA(u,v)变成了find(LCA(find(u),find(v)) 其中find(x) 是找到并查集中u的根。 很好!第一个lca的维护已经解决了。
接下来看如何维护一个点的深度。假设我们删掉一条边(u,fa(u)) fa(u) 表示的是u的父亲节点。 那么u节点对应的子树里面的所有节点的深度都需要减1,这时候我们的时间戳又起作用了,我们可以在dfs的时候记录每个点对应的最早时间L(就是根的时间)和他的子孙中最大的时间R(就是最有边的叶子的时间),不难想到一个节点的所有子孙的时间值能够涵盖[L+1,R] 中的每一个整数。所以我们可以用线段树来维护一颗子树中的深度总和。每删掉一条边(u,fa(u))就是将L[u],R[u]中的值全部-1。这样就实现了一个节点所有子孙的深度都-1的效果。
这道题目说到这里基本上就已经解出来了。剩下就是考验你码力的时候了。然后的话由于点有30000,递归最多要到30000层,所以直接递归的dfs可能会溢出,所以我用的是栈模拟递归。虽然是这么说,但是别人的用递归的代码还是过了Orz。。。。
最后附上三个链接:
http://blog.sina.com.cn/s/blog_51cea4040100gjwg.html
http://blog.sina.com.cn/s/blog_51cea4040100gjxe.html
http://blog.csdn.net/wsx1754175/article/details/22519417
这两个是这道题目的题解。如果我讲得不够清楚,可以结合这两个里面的看,相信你们一定能搞懂这道题目的!而且里面有一个很不错的线段树的写法,大家可以参考一下哦
//离线+倒序 操作 //Seg_Tree + LCA + RMQ + 并查集 #include<iostream> #include<cstring> #include<cstdio> #include<string> #include<vector> #include<algorithm> #include<queue> #include<stack> using namespace std; #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 const int maxn = 30000 + 10; const int maxm = 100000 + 10; const int maxq = maxm + 40000 + 10; struct Edge{ int u,v; Edge() { } Edge(int u,int v) :u(u),v(v) { } bool operator < (const Edge &a ) const { if(u==a.u) return v<a.v; return u < a.u; } bool operator == (const Edge &a ) const { return ( u == a.u && v == a.v ); } }e[maxm]; struct Oper{ int c,a,b,ans; Oper() { } Oper(int c,int a,int b) :c(c),a(a),b(b) { } }oper[maxq]; struct Tree{ int sum[maxn<<2],col[maxn<<2]; void Push_Up(int rt){ sum[rt]=sum[rt<<1] + sum[rt<<1|1]; } void Push_Down(int rt,int l,int r){ if(col[rt]){ col[rt<<1]+=col[rt];col[rt<<1|1]+=col[rt]; int m=(l+r)>>1; sum[rt<<1]+=col[rt]*(m-l+1); sum[rt<<1|1]+=col[rt]*(r-m); col[rt]=0; } } void UpDate(int L,int R,int x,int l,int r,int rt){ if(L <= l&&r <= R){ sum[rt]+=x*(r-l+1); col[rt]+=x; return ; } Push_Down(rt,l,r); int m = (l+r)>>1; if(L<=m) UpDate(L,R,x,lson); if(R>m) UpDate(L,R,x,rson); Push_Up(rt); } int Query(int p,int l,int r,int rt){ if(l==r) return sum[rt]; int m=(l+r)>>1; Push_Down(rt,l,r); if(p<=m) return Query(p,lson); else return Query(p,rson); } void init(){ memset(sum,0,sizeof sum ); memset(col,0,sizeof col ); } }Seg_Tree;//线段树 int N,M,Q,dfn,pre[maxn],post[maxn]; bool vis[maxm],del[maxm]; int rmq[maxm*2],tot; int dfn_node[maxn],rmq_pos[maxn]; int ans[maxm*2][19]; int dep[maxn],p[maxn],fa[maxn]; vector<int> G[maxn]; int find(int x){ if(x==p[x]) return x; else return p[x]=find(p[x]); } void make_rmq(){ for(int i=0;i<tot;i++) ans[i][0]=rmq[i]; for(int j=1;(1<<j)<=tot;j++) for(int i=0;i+(1<<j)-1 < tot;i++) ans[i][j]=min(ans[i][j-1],ans[i+(1<<(j-1))][j-1]); } stack<int> S; void DFS(){ while(S.size()) S.pop(); S.push(1); memset(pre,0,sizeof pre ); dep[1]=0; memset(fa,-1,sizeof fa ); fa[1]=1; dfn=tot=0; memset(vis,0,sizeof vis ); while(S.size()){ int u=S.top(); if(pre[u]){ post[u]=dfn; if(fa[u]>0) rmq[tot++]=pre[fa[u]]; S.pop(); continue; } pre[u]= ++dfn;rmq_pos[u]=tot; rmq[tot++]=dfn;dfn_node[dfn]=u; for(int i=0;i<G[u].size();i++){ int x=G[u][i]; if(vis[x]||del[x]) continue; vis[x]=true; Edge &z = e[x]; int v= z.u==u ? z.v : z.u; if(fa[v]!=-1){ oper[Q].c=0; oper[Q].a=min(u,v); oper[Q].b=max(u,v); ++Q; } else { fa[v]=u; dep[v]=dep[u]+1; S.push(v); } } } Seg_Tree.init(); for(int i=1;i<=N;i++) Seg_Tree.UpDate(pre[i],pre[i],dep[i],1,N,1); } void INTPut(){ for(int i=0;i<M;i++){ int u,v; scanf("%d%d",&u,&v); if(u>v) swap(u,v); e[i]=Edge(u,v); } sort(e,e+M);Q=0; memset(del,0,sizeof del ); int c,a,b; while(scanf("%d",&c)==1){ if(c==-1) break; scanf("%d%d",&a,&b); if(a>b) swap(a,b); oper[Q++]=Oper(c,a,b); if(c==0){ int z=lower_bound(e,e+M,Edge(a,b)) - e; del[z]=true; } } } void Build_Tree(){ for(int i=1;i<=N;i++) G[i].clear(),p[i]=i; for(int i=0;i<M;i++) if(!del[i]){ int u=e[i].u,v=e[i].v; G[u].push_back(i); G[v].push_back(i); } DFS(); make_rmq(); } int LCA(int u,int v){ u=find(u);v=find(v); u=rmq_pos[u];v=rmq_pos[v]; if(u>v) swap(u,v); int k=0; while((1<<(k+1))<=v-u+1) k++; return dfn_node[min(ans[u][k],ans[v-(1<<k)+1][k])]; } void Destroy(int u,int rt){ while(u!=rt){ Seg_Tree.UpDate(pre[u],post[u],-1,1,N,1); p[u]=rt; u=find(fa[u]); } } void UpDate(int u,int v){ u=find(u);v=find(v); int lca=find(LCA(u,v)); Destroy(u,lca);Destroy(v,lca); } int Query(int u,int v){ u=find(u);v=find(v); int lca=find(LCA(u,v)); int deepu=Seg_Tree.Query(pre[u],1,N,1); int deepv=Seg_Tree.Query(pre[v],1,N,1); int deeplca=Seg_Tree.Query(pre[lca],1,N,1); return deepu+deepv-deeplca*2; } void Solve(){ for(int i=Q-1;i>=0;i--) if(oper[i].c==0) UpDate(oper[i].a,oper[i].b); else oper[i].ans=Query(oper[i].a,oper[i].b); for(int i=0;i<Q;i++) if(oper[i].c==1) printf("%d\n",oper[i].ans); } int main(){ while(scanf("%d%d",&N,&M)==2){ INTPut(); Build_Tree(); Solve(); } return 0; }
相关文章推荐
- [BZOJ1969][Ahoi2005]LANE 航线规划(树链剖分+并查集)
- [AHOI2005]【bzoj1969】LANE 航线规划——LCT维护双联通分量
- Bzoj1969: [Ahoi2005]LANE 航线规划
- 【BZOJ 1969】 1969: [Ahoi2005]LANE 航线规划 (树链剖分+线段树)
- 【BZOJ1969】[Ahoi2005]LANE 航线规划 离线+树链剖分+线段树
- BZOJ 1969: [Ahoi2005]LANE 航线规划
- Bzoj1969: [Ahoi2005]LANE 航线规划
- bzoj 1969: [Ahoi2005]LANE 航线规划
- 【AHOI2005】【BZOJ1969】LANE 航线规划
- bzoj 1969: [Ahoi2005]LANE 航线规划 离线+树链剖分+线段树
- 【BZOJ】1969: [Ahoi2005]LANE 航线规划
- AHOI2005航线规划 bzoj1969(LCT缩点)
- [bzoj1969] [Ahoi2005]LANE 航线规划
- 【动态缩点】【bzoj 1969】: [Ahoi2005]LANE 航线规划
- ●BZOJ 1969 [Ahoi2005]LANE 航线规划
- [BZOJ1969][Ahoi2005]LANE 航线规划(树链剖分+并查集)
- bzoj 1969: [Ahoi2005]LANE 航线规划(树链剖分+线段树+最小生成树)
- BZOJ 1969: [Ahoi2005]LANE 航线规划( 树链剖分 )
- BZOJ 1969: [Ahoi2005]LANE 航线规划 [树链剖分 时间倒流]
- [BZOJ1969][Ahoi2005]LANE 航线规划(树链剖分)