您的位置:首页 > 其它

BZOJ 1787 裸LCA

2015-12-16 21:22 253 查看
一个求树上LCA的裸题。。WC(伪。。)里想出来的。。

题目大意:给出树上的三个点,要求确定一个集合点,使得这三个点到集合点的路径权值和最小。所有边权均为1。

先考虑两个点A、B的情形。。显然这两个点间路径上的任何一点都可以作为集合点。。

然后再加入第三个点C。。画个图不难证明此时最优集合点应是LCA(A, B)。

但是A、B、C分别是哪个点呢?。。

枚举取最优解。。

但看了黄学长博客,给出了两种解法。。一种就是枚举,另一种则给出了一句结论,私认为很有道理。。

“求出两两lca,其中有两个相同,答案则为另一个,画画图就可以理解”

这样的话,就可以直接确定是那一个LCA。。不用判断了。。

所以答案就很好搞了。。求求LCA加加就行了。。

用的是黄学长的模板。。

// BZOJ 1787

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

typedef long long LL;
typedef unsigned long long uLL;
const int N=500000+5, INF=0x3f3f3f3f;

#define rep(i,a,b) for (int i=a; i<=b; i++)
#define dep(i,a,b) for (int i=a; i>=b; i--)
#define read(x) scanf("%d", &x)
#define fill(a,x) memset(a, x, sizeof(a))

int n, q, dep
, last
, fa
[20];

struct Edge {
int to, pre;
} e[N*2];  // 注意无向边数要乘2!

int k=0;
void ine(int x, int y) {
k++;
e[k].to=y; e[k].pre=last[x]; last[x]=k;
k++;
e[k].to=x; e[k].pre=last[y]; last[y]=k;
}
#define reg(i,a) for (int i=last[a]; i; i=e[i].pre)

void change(int x, int dad) {  // 无根树转有根树的同时递推每个点的2^i祖先
rep(i,1,18) {
if (dep[x]<(1<<i)) break;
fa[x][i]=fa[fa[x][i-1]][i-1];
}
reg(i,x) {
int y=e[i].to;
if (y==dad) continue;
dep[y]=dep[x]+1;
fa[y][0]=x;
change(e[i].to, x);
}
}

int LCA(int x, int y) {
if (dep[x]<dep[y]) swap(x, y);
int d=dep[x]-dep[y];
rep(i,0,18)
if ((1<<i)&d) x=fa[x][i];
dep(i,18,0)
if (fa[x][i]!=fa[y][i]) { x=fa[x][i]; y=fa[y][i]; }
if (x==y) return x; else return fa[x][0];
}

int dis(int x, int y) { int t=LCA(x,y); return dep[x]+dep[y]-2*dep[t]; }

void solve(int x, int y, int z) {
int p1=LCA(x, y), p2=LCA(x, z), p3=LCA(y, z), p, ans;
if (p1==p2) p=p3; else if (p2==p3) p=p1; else p=p2;
ans=dis(x, p)+dis(y, p)+dis(z, p);
printf("%d %d\n", p, ans);
}

int main()
{
read(n); read(q);
rep(i,1,n-1) {
int u, v;
read(u); read(v);
ine(u, v);
}
change(1, 0);
rep(i,1,q) {
int x, y, z;
read(x); read(y); read(z);
solve(x, y, z);
}

return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: