BZOJ 1969: [Ahoi2005]LANE 航线规划( 树链剖分 )

首先我们要时光倒流, 倒着做, 变成加边操作维护关键边. 先随意搞出一颗树, 树上每条边都是关键边(因为是树, 去掉就不连通了)....然后加边(u, v)时, 路径(u, v)上的所有边都变成非关键边了, 因为形成了环, 环上任意2点有2条路径。。。下图, 加上蓝色的边, 红色x的边就变成了非关键边.

所以树链剖分维护一下...时间复杂度O(NlogN+MlogM+Qlog^2N), 可以AC.

翻了翻ZY的标解:“动态维护树+最近公共祖先查询”....复杂度是O(NlogN+M+QlogN)的.比我不知道高到那里去.....不过他的解法太神了orz(其实是懒得看= =

----------------------------------------------------------------------------------------

#include<cstdio>

#include<cctype>

#include<cstring>

#include<algorithm>

using namespace std;

typedef pair<int, int> pii;

const int maxn = 30009;

const int maxM = 100009;

const int maxQ = 40009;

int par[maxn], F[maxM], ans[maxQ];

int M, N, qn, n, L, R;

int fa[maxn], ch[maxn], top[maxn], sz[maxn], dep[maxn], Id[maxn], TOP;

inline int getint() {

char c = getchar();

for(; !isdigit(c); c = getchar())

if(c == ‘-‘) return  -1;

int ret = 0;

for(; isdigit(c); c = getchar())

ret = ret * 10 + c - ‘0‘;

return ret;

}

struct Q {

int t, u, v;

} q[maxQ];

struct E {

int u, v;

E() {}

E(int _u, int _v) : u(_u), v(_v) {

}

bool operator < (const E &p) const {

return u < p.u || (u == p.u && v < p.v);

}

} e[maxM];

struct edge {

int t;

edge* n;

} EDGE[maxn << 1], *Pt = EDGE, *H[maxn];

inline void AddEdge(int u, int v) {

Pt->t = v, Pt->n = H[u], H[u] = Pt++;

}

int Find(int x) {

return x == par[x] ? x : par[x] = Find(par[x]);

}

void dfs(int x) {

sz[x] = 1, ch[x] = -1;

for(edge* e = H[x]; e; e = e->n) if(e->t != fa[x]) {

fa[e->t] = x;

dep[e->t] = dep[x] + 1;

dfs(e->t);

sz[x] += sz[e->t];

if(!~ch[x] || sz[ch[x]] < sz[e->t]) ch[x] = e->t;

}

}

void DFS(int x) {

top[x] = TOP;

Id[x] = ++n;

if(~ch[x]) DFS(ch[x]);

for(edge* e = H[x]; e; e = e->n)

if(e->t != fa[x] && e->t != ch[x]) DFS(TOP = e->t);

}

struct Node {

Node *lc, *rc;

int v, t;

inline void upd() {

if(t) {

v = 0;

} else if(lc)

v = lc->v + rc->v;

}

inline void pd() {

if(t && lc) {

lc->t = rc->t = 1;

t = 0;

}

}

} pool[maxn << 1], *pt = pool, *Root;

void Build(Node* t, int l, int r) {

t->v = r - l + 1;

t->t = 0;

if(l != r) {

int m = (l + r) >> 1;

Build(t->lc = pt++, l, m);

Build(t->rc = pt++, m + 1, r);

} else

t->lc = t->rc = NULL;

}

void Change(Node* t, int l, int r) {

if(!t->v) return;

if(L <= l && r <= R) {

t->t = 1;

} else {

int m = (l + r) >> 1;

if(L <= m) Change(t->lc, l, m);

if(m < R) Change(t->rc, m + 1, r);

}

t->upd();

}

int Sum(Node* t, int l, int r) {

if(L <= l && r <= R)

return t->v;

int m = (l + r) >> 1;

t->pd(), t->lc->upd(), t->rc->upd();

return (L <= m ? Sum(t->lc, l, m) : 0) + (m < R ? Sum(t->rc, m + 1, r) : 0);

}

void Modify(int u, int v) {

for(; top[u] != top[v]; u = fa[top[u]]) {

if(dep[top[u]] < dep[top[v]]) swap(u, v);

L = Id[top[u]], R = Id[u];

Change(Root, 1, N);

}

if(u == v) return;

if(dep[u] > dep[v]) swap(u, v);

L = Id[u] + 1, R = Id[v];

Change(Root, 1, N);

}

int Query(int u, int v) {

int ret = 0;

for(; top[u] != top[v]; u = fa[top[u]]) {

if(dep[top[u]] < dep[top[v]]) swap(u, v);

L = Id[top[u]], R = Id[u];

ret += Sum(Root, 1, N);

}

if(dep[u] > dep[v]) swap(u, v);

L = Id[u] + 1, R = Id[v];

return ret + Sum(Root, 1, N);

}

void Work() {

for(int i = 0; i < N; i++) par[i] = i;

for(int i = 0; i < M; i++) if(F[i]) {

int u = Find(e[i].u), v = Find(e[i].v);

if(u != v) {

AddEdge(e[i].u, e[i].v);

AddEdge(e[i].v, e[i].u);

par[u] = v;

} else

F[i] = -1;

}

fa[0] = -1, dep[0] = 0, dfs(0);

DFS(TOP = 0);

Build(Root = pt++, 1, N);

for(int i = 0; i < M; i++)

if(!~F[i]) Modify(e[i].u, e[i].v);

for(int i = qn; i--; ) if(q[i].t) {

ans[i] = Query(q[i].u, q[i].v);

} else

Modify(q[i].u, q[i].v);

for(int i = 0; i < qn; i++)

if(q[i].t) printf("%d\n", ans[i]);

}

void Init() {

N = getint(), M = getint();

for(int i = 0; i < M; i++) {

e[i].u = getint() - 1, e[i].v = getint() - 1;

if(e[i].u > e[i].v) swap(e[i].u, e[i].v);

F[i] = 1;

}

sort(e, e + M);

qn = 0;

while((q[qn].t = getint()) != -1) {

q[qn].u = getint() - 1, q[qn].v = getint() - 1;

if(q[qn].u > q[qn].v) swap(q[qn].u, q[qn].v);

if(!q[qn].t) {

F[lower_bound(e, e + M, E(q[qn].u, q[qn].v)) - e] = 0;

}

qn++;

}

}

int main() {

Init();

Work();

return 0;

}

----------------------------------------------------------------------------------------

1969: [Ahoi2005]LANE 航线规划

Time Limit: 10 Sec  Memory Limit: 64 MB
Submit: 180  Solved: 87
[Submit][Status][Discuss]

Description

对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。 小联的任务是,不断关注航线被破坏的情况,并随时给出两个星球之间的关键航线数目。现在请你帮助完成。

Input

第一行有两个整数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。

Output

对每个C为1的询问,输出一行一个整数表示关键航线数目。 注意:我们保证无论航线如何被破坏,任意时刻任意两个星球都能够相互到达。在整个数据中,任意两个星球之间最多只可能存在一条直接的航线。

Sample Input

5 5
1 2
1 3
3 4
4 5
4 2
1 1 5
0 4 2
1 5 1
-1

Sample Output

1
3

HINT

Source

时间: 2024-08-07 10:07:54

BZOJ 1969: [Ahoi2005]LANE 航线规划( 树链剖分 )的相关文章

BZOJ 1969: [Ahoi2005]LANE 航线规划 [树链剖分 时间倒流]

题意: 一张图,删除边,求两点之间的割边数量.保证任意时刻图连通 任求一棵生成树,只有树边可能是割边 时间倒流,加入一条边,就是两点路径上的边都不可能是割边,区间覆盖... 然后本题需要把边哈希一下,手写哈希比map快很多 貌似还有一种不用树剖的做法,不管了 #include <iostream> #include <cstdio> #include <cstring> #include <algorithm> using namespace std; co

bzoj1969 [Ahoi2005]LANE 航线规划 树链剖分

题目传送门 https://lydsy.com/JudgeOnline/problem.php?id=1969 题解 如果我们把整个图边双联通地缩点,那么最终会形成一棵树的样子. 那么在这棵树上,\(x\) 和 \(y\) 两个点的答案就是它们之间的不在环中的边的数量. 现在考虑动态维护每一条边在不在环中.发现动态删除的话不太好做,所以时光反转,改成插入一条边. 先随便建立一棵生成树,然后如果插入一条非树边,那么两个端点之间的边就都是在环中的边了. 用树剖维护就可以了. 时间复杂度 \(O(q\

Luogu2542 AHOI2005 航线规划 树链剖分、线段树

传送门 看到删边不用想就是反着加边 先把删完边之后的图拆一个生成树出来,然后考虑非树边的影响.实际上非树边就是让树上的一条路径的权值从$1$变为了$0$,而每一个询问就是一条路径上的权值之和.使用树链剖分+线段树维护权值即可. 1 #include<bits/stdc++.h> 2 #define lch (now << 1) 3 #define rch (now << 1 | 1) 4 #define mid ((l + r) >> 1) 5 //This

[AHOI2005]航线规划(树链剖分+时间倒流)

传送门 练一下树剖的板子,运用一下时间倒流和下放边权的思想. 题中所谓“关键航线”其实就是桥. 删边操作桥不好维护,但如果是加边,每加一条边,两点作为端点的这条路径就都不再是桥----->考虑时间倒流. 从后往前,每删除一条边,现在就是加边,该路径上所有边都不是桥(打上标记). 可以先求出一棵最小生成树(代码中是在dfs中实现的)那些多的边就以加边的方式加入(说明到最后一个操作后,这条路径的边也不是桥). #include<bits/stdc++.h> #define M 200003

luogu2542 航线规划 (树链剖分)

不会lct,所以只能树剖乱搞 一般这种删边的题都是离线倒着做,变成加边 他要求的结果其实就是缩点以后两点间的距离. 然后先根据最后剩下的边随便做出一个生成树,然后假装把剩下的边当成加边操作以后处理 这样的话,就可以做树剖来维护现在的两点间距离. 然后考虑加边,其实就是加了一条边然后某一处成环了,缩成了一个点. 这样的话,就可以把这条边两个端点间的距离都改成0,假装缩了点. 最后再倒着输出答案就行了. 1 #include<cstdio> 2 #include<cstring> 3

【BZOJ1969】[Ahoi2005]LANE 航线规划 离线+树链剖分+线段树

[BZOJ1969][Ahoi2005]LANE 航线规划 Description 对Samuel星球的探险已经取得了非常巨大的成就,于是科学家们将目光投向了Samuel星球所在的星系——一个巨大的由千百万星球构成的Samuel星系. 星际空间站的Samuel II巨型计算机经过长期探测,已经锁定了Samuel星系中许多星球的空间坐标,并对这些星球从1开始编号1.2.3……. 一些先遣飞船已经出发,在星球之间开辟探险航线. 探险航线是双向的,例如从1号星球到3号星球开辟探险航线,那么从3号星球到

【bzoj1959】[Ahoi2005]LANE 航线规划 离线处理+树链剖分+线段树

题目描述 对Samuel星球的探险已经取得了非常巨大的成就,于是科学家们将目光投向了Samuel星球所在的星系——一个巨大的由千百万星球构成的Samuel星系. 星际空间站的Samuel II巨型计算机经过长期探测,已经锁定了Samuel星系中许多星球的空间坐标,并对这些星球从1开始编号1.2.3……. 一些先遣飞船已经出发,在星球之间开辟探险航线. 探险航线是双向的,例如从1号星球到3号星球开辟探险航线,那么从3号星球到1号星球也可以使用这条航线. 例如下图所示: 在5个星球之间,有5条探险航

[bzoj1969] [Ahoi2005]LANE 航线规划

tarjan.并查集.树状数组.树链剖分. 时间倒流,变删边为加边. 先求一波边双联通分量,缩点. 题目保证最后还是整张图联通的..所以就是一棵树. 现在的操作就是,将路径上的边权置0(加边时),查询两点间边权和. 可以转换成求根到点路径上边权和,置0的时候,就相当于将子树内的值都减去原边的长度,可以dfs序+树状数组. 置0那个还要写个并查集,维护当前点经过连续一段0边到达的最远的祖先. 查询的时候还得求lca....就顺便写个链剖... 1 #include<cstdio> 2 #incl

Bzoj 4196: [Noi2015]软件包管理器 树链剖分

4196: [Noi2015]软件包管理器 Time Limit: 10 Sec  Memory Limit: 512 MBSubmit: 721  Solved: 419[Submit][Status][Discuss] Description Linux用户和OSX用户一定对软件包管理器不会陌生.通过软件包管理器,你可以通过一行命令安装某一个软件包,然后软件包管理器会帮助你从软件源下载软件包,同时自动解决所有的依赖(即下载安装这个软件包的安装所依赖的其它软件包),完成所有的配置.Debian