您的位置:首页 > 其它

洛谷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

这两个是这道题目的题解。如果我讲得不够清楚,可以结合这两个里面的看,相信你们一定能搞懂这道题目的!而且里面有一个很不错的线段树的写法,大家可以参考一下哦

//离线+倒序 操作
//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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  RMQ LCA 线段树