https://www.codechef.com/LTIME14/problems/TALCA
LCA:最近公共祖先,对于有根树T的两个节点u,v,最近公共祖先LCA(T,u,v)表示一个节点x,满足x是u,v的祖先且x的深度depth尽可能大。
终于把LCA这棵树给调试好了,在错误中不断修改,用了新学的离线操作,还有前向星存图,也只帮我过了中级测试,高级测试TLE,因为没有发现其中的技巧。
LCA板子
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
const int N = 300111, logN = 20;
vector <int> adj[N]; //若需存边全的信息,把int换为结构体
int f[N][logN], dep[N], n;
void dfs(int u) {
for (int i = 1; i < logN; i++)
f[u][i] = f[f[u][i - 1]][i - 1];
for (int i = 0; i < (int)adj[u].size(); i++) {
int v = adj[u][i];
if (!dep[v]) {
f[v][0] = u;
dep[v] = dep[u] + 1;
dfs(v);
}
}
}
int lca(int u, int v) {
if (dep[u] < dep[v]) swap(u, v);
for (int i = logN - 1; i >= 0; i--)
if (dep[f[u][i]] >= dep[v]) {
u = f[u][i];
}
if (u == v) return u;
for (int i = logN - 1; i >= 0; i--)
if (f[u][i] != f[v][i]) {
u = f[u][i];
v = f[v][i];
}
return f[u][0];
}
int dist(int u, int v) { //若边全不为1,就不能用这个函数就距离了
int x = lca(u, v);
int res = dep[u] + dep[v] - 2 * dep[x];
return res;
}
#define _CRT_SECURE_NO_WARNINGS
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 2 * 1e5 + 7, H = 20;
int n, tot, root; //树的根节点
int head[N], Head[N]; //存的边的信息,head[i]表示第i个节点的头指针
int point[N << 1]; //第i条边指向的节点
int Next[N << 1]; //第i条边的下一个指针
int anc[N][H]; //对于每一个节点v,记录anc[v][k],表示它向上走pow(2,k)步之后到达的节点
int Stack[N], dep[N];
void dfs(int root) {
int top = 0;
dep[root] = 1;
for (int i = 0; i < H; ++i)
anc[root][i] = root;
Stack[++top] = root; //先求出anc[v][0]
memcpy(head, Head, sizeof Head);//Head为原始的第i个节点的头指针
while (top) {
int x = Stack[top];
if (x != root) {
for (int i = 1; i < H; ++i) { //再求出其他anc[v][k]
int y = anc[x][i - 1];
anc[x][i] = anc[y][i - 1];
}
}
for (int &i = head[x]; ~i; i = Next[i]) { //这里i为引用,会修改head
int y = point[i];
if (y != anc[x][0]) {
dep[y] = dep[x] + 1;
anc[y][0] = x;
Stack[++top] = y;
}
}
while (top && head[Stack[top]] == -1) //==-1,这个-1和初始Head有关
top--;
}
}
void swim(int &x, int k) { //从节点x向上移动k步,并将x赋为新走到的节点
for (int i = 0; k > 0; ++i) {
if (k & 1)
x = anc[x][i];
k /= 2;
}
}
int lca(int x, int y) { //寻找x, y的LCA。
int k;
if (dep[x] > dep[y]) swap(x, y);
swim(y, dep[y] - dep[x]); //首先利用swim将x,y调整到同一高度
if (x == y) return x; //若x和y重合,就是我们要找的LCA
while (true) { //否则,不断第寻找一个最小的k,使得anc[x][k] = anc[y][k]
for (k = 0; anc[x][k] != anc[y][k]; ++k);
if (k == 0)
return anc[x][0];
x = anc[x][k - 1];//新的x,y和原来的x,y有相同的LCA
y = anc[y][k - 1];
}
return -1;
}
void init() {
tot = 0;
memset(Head, -1, sizeof Head);
}
void add(int u, int v) {
point[tot] = v;
Next[tot] = Head[u];
Head[u] = tot++;
}
struct Query {
int r, u, v, pos;
bool operator < (const Query& tmp) const {
return r < tmp.r;
}
}Q[N];
int ans[N];
int main()
{
while (~scanf("%d", &n)) {
init();
int u, v;
for (int i = 1; i < n; ++i) { //读入n-1条边
scanf("%d%d", &u, &v);
add(u, v);
add(v, u);//无向边
}
int m;
scanf("%d", &m);
for (int i = 0; i < m; ++i) {
scanf("%d%d%d", &Q[i].r, &Q[i].u, &Q[i].v);
Q[i].pos = i;
}
sort(Q, Q + m);
int preRoot = -1;
for (int i = 0; i < m; ++i) {
if (preRoot != Q[i].r) {
dfs(root = Q[i].r);
preRoot = Q[i].r;
}
ans[Q[i].pos] = lca(Q[i].u, Q[i].v);
}
for (int i = 0; i < m; ++i) {
printf("%d\n", ans[i]);
}
}
return 0;
}
How to get 100 points
There are two interesting observations that you can make:
-
Given the query "r u v" what can be the answer? The possible answers are r, u, v, LCA(r, u), LCA(r, v), LCA(u, v)where LCA(x, y) is LCA of x and y when the tree is rooted at 1.
-
The LCA of u and v when the root is at r is the vertex x such that the sum of shortest path from x to u, x to v and x to r is smallest.
With this two observations you need to implement two function: finding LCA and distance of the two vertices in the tree. Proof for these two observation is not hard but too long to be mentioned here. It is left as an exercise for you.
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
#define maxn 300111
#define logN 20
vector <int> adj[maxn];
int f[maxn][logN], depth[maxn], n;
void dfs(int u) {
for (int i = 1; i < logN; i++)
f[u][i] = f[f[u][i - 1]][i - 1];
for (int i = 0; i < (int)adj[u].size(); i++) {
int v = adj[u][i];
if (!depth[v]) {
f[v][0] = u;
depth[v] = depth[u] + 1;
dfs(v);
}
}
}
int lca(int u, int v) {
if (depth[u] < depth[v]) swap(u, v);
for (int i = logN - 1; i >= 0; i--)
if (depth[f[u][i]] >= depth[v]) {
u = f[u][i];
}
if (u == v) return u;
for (int i = logN - 1; i >= 0; i--)
if (f[u][i] != f[v][i]) {
u = f[u][i];
v = f[v][i];
}
return f[u][0];
}
int dist(int u, int v) {
int x = lca(u, v);
int res = depth[u] + depth[v] - 2 * depth[x];
return res;
}
int main() {
ios::sync_with_stdio(false);
cin >> n;
for (int i = 1; i < n; i++) {
int u, v;
cin >> u >> v;
adj[u].push_back(v);
adj[v].push_back(u);
}
depth[1] = 1;
dfs(1);
int q;
cin >> q;
pair<int, int> p[6];
while (q--) {
int u, v, r;
cin >> r >> u >> v;
p[0].second = u;
p[1].second = v;
p[2].second = r;
p[3].second = lca(r, u);
p[4].second = lca(r, v);
p[5].second = lca(u, v);
for (int i = 0; i < 6; i++) {
int x = p[i].second;
p[i].first = dist(x, r) + dist(x, u) + dist(x, v);
}
sort(p, p + 6);
cout << p[0].second << endl;
}
}
再附我的和大神的综合一下440ms
#define _CRT_SECURE_NO_WARNINGS
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 2 * 1e5 + 7, H = 20;
int n, tot, root; //树的根节点
int head[N], Head[N]; //存的边的信息,head[i]表示第i个节点的头指针
int point[N << 1]; //第i条边指向的节点
int Next[N << 1]; //第i条边的下一个指针
int anc[N][H]; //对于每一个节点v,记录anc[v][k],表示它向上走pow(2,k)步之后到达的节点
int Stack[N], dep[N];
void dfs(int root) {
int top = 0;
dep[root] = 1;
for (int i = 0; i < H; ++i)
anc[root][i] = root;
Stack[++top] = root; //先求出anc[v][0]
memcpy(head, Head, sizeof Head);//Head为原始的第i个节点的头指针
while (top) {
int x = Stack[top];
if (x != root) {
for (int i = 1; i < H; ++i) { //再求出其他anc[v][k]
int y = anc[x][i - 1];
anc[x][i] = anc[y][i - 1];
}
}
for (int &i = head[x]; ~i; i = Next[i]) { //这里i为引用,会修改head
int y = point[i];
if (y != anc[x][0]) {
dep[y] = dep[x] + 1;
anc[y][0] = x;
Stack[++top] = y;
}
}
while (top && head[Stack[top]] == -1) //==-1,这个-1和初始Head有关
top--;
}
}
void swim(int &x, int k) { //从节点x向上移动k步,并将x赋为新走到的节点
for (int i = 0; k > 0; ++i) {
if (k & 1)
x = anc[x][i];
k /= 2;
}
}
int lca(int x, int y) { //寻找x, y的LCA。
int k;
if (dep[x] > dep[y]) swap(x, y);
swim(y, dep[y] - dep[x]); //首先利用swim将x,y调整到同一高度
if (x == y) return x; //若x和y重合,就是我们要找的LCA
while (true) { //否则,不断第寻找一个最小的k,使得anc[x][k] = anc[y][k]
for (k = 0; anc[x][k] != anc[y][k]; ++k);
if (k == 0)
return anc[x][0];
x = anc[x][k - 1];//新的x,y和原来的x,y有相同的LCA
y = anc[y][k - 1];
}
return -1;
}
void init() {
tot = 0;
memset(Head, -1, sizeof Head);
}
void add(int u, int v) {
point[tot] = v;
Next[tot] = Head[u];
Head[u] = tot++;
}
int dist(int u, int v) {
int x = lca(u, v);
int res = dep[u] + dep[v] - 2 * dep[x];
return res;
}
int main()
{
while (~scanf("%d", &n)) {
init();
int u, v, r;
for (int i = 1; i < n; ++i) { //读入n-1条边
scanf("%d%d", &u, &v);
add(u, v);
add(v, u);//无向边
}
dfs(root = 1);
int m, ans[6];
scanf("%d", &m);
pair<int, int> p[6];
while (m--) {
int u, v, r;
scanf("%d%d%d", &r, &u, &v);
p[0].second = u;
p[1].second = v;
p[2].second = r;
p[3].second = lca(r, u);
p[4].second = lca(r, v);
p[5].second = lca(u, v);
for (int i = 0; i < 6; i++) {
int x = p[i].second;
p[i].first = dist(x, r) + dist(x, u) + dist(x, v);
}
sort(p, p + 6);
printf("%d\n", p[0].second);
}
}
return 0;
}