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
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
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
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]);
}