BZOJ3319: 黑白树

Description

给定一棵树,边的颜色为黑或白,初始时全部为白色。维护两个操作:

1.查询u到根路径上的第一条黑色边的标号。

2.将u到v 路径上的所有边的颜色设为黑色。

Notice:这棵树的根节点为1

Input

第一行两个数n,m分别表示点数和操作数。

接下来n-1行,每行2个数u,v.表示一条u到v的边。

接下来m行,每行为以下格式:

1 v 表示第一个操作

2 v u 表示第二种操作

n,m<=10^6

Output

对于每个询问,输出相应答案。如果不存在,输出0

Sample Input

5 4
1 2
1 3
2 4
2 5
1 2
2 2 3
1 3
1 4

Sample Output

0
2
1

Solution

一开始只会 \(O(n\log ^2n)\) 的树剖......看到\(n, m\le 10^6\)果断没写。
被zn提示了一下把染色理解成删边,然后我再回忆到了\(BZOJ4551\)那题的并查集做法,就有了大概的思路......
离线处理。
考虑全部染完后的树,把那些自始至终都没有被染过色的边并查集并一下并到\(dep\)最小的点为根(这个可以让每个点\(merge\)自己的父亲),就可以\(O(\log n)\)查询每个点向上到根第一条黑边所在的位置(每个点代表它连向父亲的边)。
处理出这个染完的树需要一定的技巧...首先可以再开一个并查集,因为每条边是只需要被访问一次(即使它可能被染色多次,我们要的是第一次染色的时间),所以可以在新的并查集里面把这段全都\(merge\)起来,相当于压缩了这段路径。然后利用邻接表或者\(vector\)将这段操作历经的所有点的边编号记录下来(以便后面处理)。这样处理的复杂度就是\(O(n\log n)\)的了。
倒着处理所有操作,对于每个查询操作按前面提到的做法在并查集里面查一下即可。对于染色操作,将邻接表/\(vector\)的边都取出来,将这条边的端点\(merge\)起来,就可以保证在处理查询操作时查到的是最近的黑色边了。
细节还是不是很会写......参详了这篇博客的代码......
总的时间复杂度是\(O(n \log n)\)。如果采用\(\text{tarjan LCA}\)并且并查集按秩合并+路径压缩的话可以做到\(O(n + q)\)(众所周知加了那俩优化的并查集近似线性复杂度了...)

#include <bits/stdc++.h>
using namespace std;

namespace io {
char buf[1<<21], *p1 = buf, *p2 = buf;
inline char gc() {
    if(p1 != p2) return *p1++;
    p1 = buf;
    p2 = p1 + fread(buf, 1, 1 << 21, stdin);
    return p1 == p2 ? EOF : *p1++;
}
#define G gc

#ifndef ONLINE_JUDGE
#undef G
#define G getchar
#endif

template<class I>
inline void read(I &x) {
    x = 0; I f = 1; char c = G();
    while(c < '0' || c > '9') {if(c == '-') f = -1; c = G(); }
    while(c >= '0' && c <= '9') {x = x * 10 + c - '0'; c = G(); }
    x *= f;
}
template<class I>
inline void write(I x) {
    if(x == 0) {putchar('0'); return;}
    I tmp = x > 0 ? x : -x;
    if(x < 0) putchar('-');
    int cnt = 0;
    while(tmp > 0) {
        buf[cnt++] = tmp % 10 + '0';
        tmp /= 10;
    }
    while(cnt > 0) putchar(buf[--cnt]);
}

#define in(x) read(x)
#define outn(x) write(x), putchar('\n')
#define out(x) write(x), putchar(' ')

#undef G

} using namespace io;

#define ll long long
const int N = 1000100;

int n, m, dep[N], f[N][22], id[N], Del[N], ans[N];
struct B {
    int f[N];
    void init() {for(int i = 1; i <= n; ++i) f[i] = i;}
    int find(int x) {if(f[x] == x) return x; return f[x] = find(f[x]);}
    void merge(int x, int y) {
        x = find(x); y = find(y);
        if(dep[x] < dep[y]) swap(x, y); 
        f[x] = y;
    }
} F, tmp;
struct Edge {
    int head[N], cnt;
    struct edge {int to, nxt, from, id;} e[N << 1];
    void ins(int u, int v, int id) {
        e[++cnt] = (edge) {v, head[u], u, id};
        head[u] = cnt;
    }
} G, g;
struct Q {
    int x, y, id, LCA, op;
} q[N];

void dfs(int u) {
    for(int i = G.head[u]; i; i = G.e[i].nxt) {
        int v = G.e[i].to;
        if(v == f[u][0]) continue;
        f[v][0] = u;
        for(int j = 1; j <= 20; ++j) f[v][j] = f[f[v][j - 1]][j - 1];
        dep[v] = dep[u] + 1;
        id[v] = i;
        dfs(v);
    }
}

int lca(int x, int y) {
    if(dep[x] < dep[y]) swap(x, y);
    for(int i = 20; i >= 0; --i) 
        if(dep[f[x][i]] >= dep[y]) x = f[x][i];
    if(x == y) return x;
    for(int i = 20; i >= 0; --i) 
        if(f[x][i] != f[y][i]) x = f[x][i], y = f[y][i];
    return f[x][0];
}

void cut(int x, int y, int l, int now) {
    for(x = tmp.find(x); dep[x] > dep[l]; Del[x] = 1, tmp.merge(x, f[x][0]), g.ins(now, id[x], 1), x = tmp.find(f[x][0]));
    for(y = tmp.find(y); dep[y] > dep[l]; Del[y] = 1, tmp.merge(y, f[y][0]), g.ins(now, id[y], 1), y = tmp.find(f[y][0]));
}
void link(int now) {
    for(int i = g.head[now]; i; i = g.e[i].nxt) F.merge(G.e[g.e[i].to].to, G.e[g.e[i].to].from);
}

int main() {
    read(n); read(m);
    for(int x, y, i = 1; i < n; ++i) {read(x); read(y); G.ins(x, y, i); G.ins(y, x, i);}
    dfs(1); tmp.init(); F.init();
    for(int i = 1; i <= m; ++i) {
        read(q[i].op); read(q[i].x); q[i].id = i;
        if(q[i].op == 2) {
            read(q[i].y), q[i].LCA = lca(q[i].x, q[i].y);
            cut(q[i].x, q[i].y, q[i].LCA, i);
        }
    }
    for(int i = 2; i <= n; ++i) if(!Del[i]) F.merge(i, f[i][0]);
    int tot = 0;
    for(int i = m; i; --i) {
        if(q[i].op == 2) link(q[i].id);
        else {
            int now = F.find(q[i].x);
            if(now == 1) ans[++tot] = 0;
            else ans[++tot] = G.e[id[now]].id;
        }
    }
    for(int i = tot; i; --i) outn(ans[i]);
}

转载于:https://www.cnblogs.com/henry-1202/p/11291597.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值