http://codeforces.com/contest/342/problem/E
如果把询问1存起来,每到sqrt(m)的时候再处理一次。
那么总复杂度就是msqrt(m)的。
把要变颜色的节点存起来,可以同时一次O(n)的bfs
然后就是LCA了。LCA需要倍增的做法。这题真的是个好题。。
#include <cstdio> #include <cstdlib> #include <cstring> #include <cmath> #include <algorithm> #include <assert.h> #define IOS ios::sync_with_stdio(false) using namespace std; #define inf (0x3f3f3f3f) typedef long long int LL; #include <iostream> #include <sstream> #include <vector> #include <set> #include <map> #include <queue> #include <string> const int maxn = 2e5 + 20; int col[maxn]; struct node { int u, v; int tonext; }e[maxn]; int first[maxn]; int num; void add(int u, int v) { ++num; e[num].u = u; e[num].v = v; e[num].tonext = first[u]; first[u] = num; } int tot[maxn]; int lentot; int dp[maxn]; struct bfsnode { int cur, cnt; bfsnode(int a, int b) : cur(a), cnt(b) {} }; queue<struct bfsnode>que; bool vis[maxn]; void bfs() { memset(vis, false, sizeof vis); for (int i = 1; i <= lentot; ++i) { que.push(bfsnode(tot[i], 0)); dp[tot[i]] = 0; } while (!que.empty()) { struct bfsnode t = que.front(); que.pop(); for (int i = first[t.cur]; i; i = e[i].tonext) { int v = e[i].v; if (vis[v]) continue; if (dp[v] <= t.cnt + 1) continue; vis[v] = true; dp[v] = t.cnt + 1; que.push(bfsnode(v, t.cnt + 1)); } } } int ansc[maxn][25], deep[maxn], fa[maxn]; void init_LCA(int cur) { ansc[cur][0] = fa[cur]; //跳1步,那么祖先就是爸爸 for (int i = 1; i <= 24; ++i) { //倍增思路,递归处理 ansc[cur][i] = ansc[ansc[cur][i - 1]][i - 1]; } for (int i = first[cur]; i; i = e[i].tonext) { int v = e[i].v; if (v == fa[cur]) continue; fa[v] = cur; deep[v] = deep[cur] + 1; init_LCA(v); } } int LCA(int x, int y) { if (deep[x] < deep[y]) swap(x, y); //需要x是最深的 for (int i = 24; i >= 0; --i) { //从大到小枚举,因为小的更灵活 if (deep[ansc[x][i]] >= deep[y]) { //深度相同,走进去就对了。 x = ansc[x][i]; } } if (x == y) return x; for (int i = 24; i >= 0; --i) { if (ansc[x][i] != ansc[y][i]) { //走到第一个不等的地方, x = ansc[x][i]; y = ansc[y][i]; } } return ansc[x][0]; //再跳一步就是答案 } int dis[maxn]; void dfs(int cur, int step) { dis[cur] = min(dis[cur], step); for (int i = first[cur]; i; i = e[i].tonext) { int v = e[i].v; if (vis[v]) continue; vis[v] = true; dfs(v, step + 1); } } void work() { int n, m; scanf("%d%d", &n, &m); for (int i = 1; i <= n - 1; ++i) { int u, v; scanf("%d%d", &u, &v); add(u, v); add(v, u); } memset(dis, 0x3f, sizeof dis); dis[1] = 0; vis[1] = true; dfs(1, 0); memset(dp, 0x3f, sizeof dp); col[1] = 1; tot[++lentot] = 1; bfs(); lentot = 0; fa[1] = 1; deep[1] = 0; init_LCA(1); int magic = (int)sqrt(m); for (int i = 1; i <= m; ++i) { int flag, which; scanf("%d%d", &flag, &which); if (flag == 1) { tot[++lentot] = which; } else { int ans = dp[which]; for (int i = 1; i <= lentot; ++i) { int haha = LCA(which, tot[i]); ans = min(ans, dis[which] + dis[tot[i]] - 2 * dis[haha]); } printf("%d ", ans); } if (lentot >= magic) { bfs(); lentot = 0; } } } int main() { #ifdef local freopen("data.txt", "r", stdin); // freopen("data.txt", "w", stdout); #endif work(); return 0; }
LCA是用在树中的,图的不行。
LCA[u][v]表示节点u和v的最近公共祖先,也是深度最大祖先,深度越大的话,证明离u和v越近嘛。这个算法基于dfs的回溯和并查集实现。dfs的时候,搜索到叶子节点(没有儿子)的时候,得到LCA[u][u]=u和LCA[u][fa]=fa,然后,返回到他爸爸那里,并查集合并,f[u]=fa;表明u的爸爸是fa,所以这个时候并查集是向左看齐的,merge(u,v),u只能是爸爸。复杂度O(n²)的算法,能求出整棵树的所有LCA[i][j]值。并查集那里有点奇葩,它也用作了标记数组的作用,所以一开始的并查集,全部是0。
void dfs(int u) {
f[u] = u; //首先自己是一个集合
for (int i = first[u]; i; i = e[i].next) {
int v = e[i].v;
if (f[v] == 0) {
dfs(v);
merge(u, v);
}
}
for (int i = 1; i <= n; i++) { //遍历每一个点
if (f[i]) { //已经确定过的,就更新LCA
LCA[u][i] = LCA[i][u] = find(i);
}
}
return ;
}
O(n+Q)算法,用邻接表存取所有询问,要询问的再处理即可,注意去重操作。
void dfs(int u) {
f[u] = u; //首先自己是一个集合
for (int i = first[u]; i; i = e[i].next) {
int v = e[i].v;
if (f[v] == 0) {
dfs(v);
merge(u, v);
}
}
for (int i = first_query[u]; i; i = query[i].next) {
int v = query[i].v;
if (f[v]) { //确定过的话,并且有要求查询
//要求查询的话这个query保存着,first_query[u]就证明有没了
//因为插边插了两次,这里要去重。用id保存答案即可
ans[query[i].id] = find(v);
}
}
return ;
}
LCA倍增算法。
设ansc[cur][i]表示从cur这个节点跳2i步到达的祖先是谁。记录深度数组deep[cur]。深度从0开始,然后算LCA的时候就先把他们弄到同一深度,然后一起倍增。
Hint:ans[root][3]是自己,都是root。开始的时候fa[root] = root。deep[root] = 0;
一般这课树是双向的,因为可能结合bfs来做题,所以需要判断不能走到爸爸那里。
int ansc[maxn][25], deep[maxn], fa[maxn];
void init_LCA(int cur) {
ansc[cur][0] = fa[cur]; //跳1步,那么祖先就是爸爸
for (int i = 1; i <= 24; ++i) { //倍增思路,递归处理
ansc[cur][i] = ansc[ansc[cur][i - 1]][i - 1];
}
for (int i = first[cur]; i; i = e[i].tonext) {
int v = e[i].v;
if (v == fa[cur]) continue;
fa[v] = cur;
deep[v] = deep[cur] + 1;
init_LCA(v);
}
}
int LCA(int x, int y) {
if (deep[x] < deep[y]) swap(x, y); //需要x是最深的
for (int i = 24; i >= 0; --i) { //从大到小枚举,因为小的更灵活
if (deep[ansc[x][i]] >= deep[y]) { //深度相同,走进去就对了。
x = ansc[x][i];
}
}
if (x == y) return x;
for (int i = 24; i >= 0; --i) {
if (ansc[x][i] != ansc[y][i]) { //走到第一个不等的地方,
x = ansc[x][i];
y = ansc[y][i];
}
}
return ansc[x][0]; //再跳一步就是答案
}