P4271 [USACO18FEB]New Barns P 题解
真不懂这题为啥要 LCT,虽然我一开始想的也是 LCT......
结论:一个点
另一个结论:两棵树用一条边连起来的时候,新的直径一定是两棵树各自的直径的两两组合中,最长的那一条。
考虑先离线建出整棵树,倍增维护 LCA,用公式
然后按顺序操作,碰到
碰到
再考虑如何记录直径,我一开始想的是用并查集,把一条直径记录在并查集的代表元的位置。后来想想并查集都不用了,离线建树的过程中,直接求出每个点所在的树的根,把直径记录在根的位置就行了,还省掉了并查集的常数。
时间复杂度:假设操作数是 再用四毛子优化就可以做到线性了。
具体细节看代码吧:
#include <cstdio>
using namespace std;
const int N = 100010;
const int LOGN = 17;
struct Edge {
int to, nxt;
}edges[N];
int point[N][2]; //记录直径的两个端点
int fa[N][LOGN], depth[N];
int opt[N], queryu[N], idx[N];
int head[N], root[N], n, m, nedge; //root[u] 表示 u 点所在的树的根
inline int max(int x, int y) {return x > y ? x : y;}
inline void swap(int &x, int &y) {x ^= y; y ^= x; x ^= y;}
inline void addedge(int u, int v) {
edges[++nedge].to = v;
edges[nedge].nxt = head[u];
head[u] = nedge;
}
inline void dfs(int u) {
for(register int i = 1; (1 << i) <= depth[u]; ++i)
fa[u][i] = fa[fa[u][i - 1]][i - 1];
for(register int i = head[u]; i; i = edges[i].nxt) {
int v = edges[i].to;
fa[v][0] = u; depth[v] = depth[u] + 1;
if(u == 0) root[v] = v;
else root[v] = root[u];
dfs(v);
}
}
inline int LCA(int u, int v) {
if(depth[u] < depth[v]) swap(u, v);
for(register int i = LOGN - 1; i >= 0; --i)
if((1 << i) <= depth[u] - depth[v]) u = fa[u][i];
if(u == v) return u;
for(register int i = LOGN - 1; i >= 0; --i)
if(fa[u][i] != fa[v][i]) {u = fa[u][i]; v = fa[v][i];}
return fa[u][0];
}
inline int dist(int u, int v) {
return depth[u] + depth[v] - 2 * depth[LCA(u, v)];
}
int main() {
scanf("%d", &m);
for(register int i = 1; i <= m; ++i) {
char op[5];
scanf("%s %d", op, &queryu[i]);
if(op[0] == 'B') opt[i] = 1; else opt[i] = 2;
if(opt[i] == 1) {idx[i] = ++n; addedge(queryu[i] > -1 ? queryu[i] : 0, n);} //idx[i] 记录第 i 步(如果是 B 操作)新建的点的编号
}
dfs(0);
for(register int u = 1; u <= n; ++u) point[u][0] = point[u][1] = u;
for(register int i = 1; i <= m; ++i) {
if(opt[i] == 1 && queryu[i] != -1) {
int x = root[queryu[i]];
int dist1 = dist(point[x][0], point[x][1]);
int dist2 = dist(point[x][0], idx[i]), dist3 = dist(point[x][1], idx[i]);
if(dist1 >= dist2 && dist1 >= dist3) {}
else if(dist2 >= dist1 && dist2 >= dist3) point[x][1] = idx[i];
else point[x][0] = idx[i]; //三个距离选最大的记录下来(注意以上的 >= 不能写成 >,具体原因自行思考)
}
if(opt[i] == 2) {
int x = root[queryu[i]];
printf("%d\n", max(dist(point[x][0], queryu[i]), dist(point[x][1], queryu[i])));
}
}
return 0;
}