1969: [Ahoi2005]LANE 航线规划
Time Limit: 10 Sec Memory Limit: 64 MBSubmit: 132 Solved: 55
[ Submit][ Status]
Description
在5个星球之间,有5条探险航线。 A、B两星球之间,如果某条航线不存在,就无法从A星球抵达B星球,我们则称这条航线为关键航线。 显然上图中,1号与5号星球之间的关键航线有1条:即为4-5航线。 然而,在宇宙中一些未知的磁暴和行星的冲撞,使得已有的某些航线被破坏,随着越来越多的航线被破坏,探险飞船又不能及时回复这些航线,可见两个星球之间的关键航线会越来越多。 假设在上图中,航线4-2(从4号星球到2号星球)被破坏。此时,1号与5号星球之间的关键航线就有3条:1-3,3-4,4-5。 小联的任务是,不断关注航线被破坏的情况,并随时给出两个星球之间的关键航线数目。现在请你帮助完成。Input
Output
Sample Input
1 2
1 3
3 4
4 5
4 2
1 1 5
0 4 2
1 5 1
-1
Sample Output
3
HINT
Source
题意:中文题目就不解释了。
思路:我们先考虑如果没有删除边的操作,只有询问怎么做。 我们可以先求出边-双连通分量。那么整个图就是一颗树了。其中树边就是题目所说的“关键边”。那么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
这两个是这道题目的题解。如果我讲得不够清楚,可以结合这两个里面的看,相信你们一定能搞懂这道题目的!而且里面有一个很不错的线段树的写法,大家可以参考一下哦
代码:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<string.h>
#include<cstring>
#include<vector>
#include<stack>
#include<queue>
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&e) const
{
if (u == e.u) return v < e.v;
return u < e.u;
}
bool operator==(const Edge&e) const
{
return e.u == u&&e.v == v;
}
}edges[maxm];
struct Oper
{
int c, a, b;
int ans;
Oper() { }
Oper(int c, int a, int b)
:c(c), a(a), b(b) { }
}oper[maxq];
struct SegmentTree
{
int sum[maxn << 2];
int col[maxn << 2];
void PushUp(int rt) { sum[rt] = sum[rt << 1] + sum[rt << 1 | 1]; }
void PushDown(int rt, int l, int r)
{
if (col[rt])
{
col[rt << 1 | 1] += col[rt];
col[rt << 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;
}
PushDown(rt, l, r);
int m = (l + r) >> 1;
if (L <= m) Update(L, R, x, lson);
if (R>m) Update(L, R, x, rson);
PushUp(rt);
}
int Query(int p, int l, int r, int rt)
{
if (l == r) return sum[rt];
int m = (l + r) >> 1;
PushDown(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;
int dfn, pre[maxn], post[maxn];
bool vis[maxm], del[maxm];
int rmq[2 * maxm], tot;
int dfn_to_node[maxn];
int rmq_pos[maxn];
int anc[2 * maxm][19];
int dep[maxn];
vector<int> G[maxn];
int fa[maxn], p[maxn];
int find(int x)
{
if (x == p[x]) return x;
return p[x] = find(p[x]);
}
void make_rmq()
{
for (int i = 0; i < tot; ++i)
anc[i][0] = rmq[i];
for (int j = 1; (1<<j)<=tot;++j)
for (int i = 0; i + (1 << j) - 1 < tot; ++i)
anc[i][j] = min(anc[i][j - 1], anc[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_to_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&e = edges[x];
int v = e.u == u ? e.v : e.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 input()
{
for (int i = 0; i < M; ++i)
{
int u, v;
scanf("%d%d", &u, &v);
if (u>v) swap(u, v);
edges[i] = Edge(u, v);
}
sort(edges, edges + M);
memset(del, 0, sizeof(del));
Q = 0;
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(edges, edges + M, Edge(a, b)) - edges;
del[z] = true;
}
}
}
void buildtree()
{
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 = edges[i].u, v = edges[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_to_node[min(anc[u][k], anc[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 - 2 * deeplca;
}
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()
{
// freopen("intpu.in", "r", stdin);
while (scanf("%d%d", &N, &M) == 2)
{
input();
buildtree();
solve();
}
}