ZOJ 3820 Building Fire Stations(树上的问题)

来源:互联网 发布:linux c 获取ntp时间 编辑:程序博客网 时间:2024/06/06 09:41

题目链接

Building Fire Stations

Time Limit: 5 Seconds      Memory Limit: 131072 KB      Special Judge

Marjar University is a beautiful and peaceful place. There are N buildings andN - 1 bidirectional roads in the campus. These buildings are connected by roads in such a way that there is exactly one path between any two buildings. By coincidence, the length of each road is 1 unit.

To ensure the campus security, Edward, the headmaster of Marjar University, plans to setup two fire stations in two different buildings so that firefighters are able to arrive at the scene of the fire as soon as possible whenever fires occur. That means the longest distance between a building and its nearest fire station should be as short as possible.

As a clever and diligent student in Marjar University, you are asked to write a program to complete the plan. Please find out two proper buildings to setup the fire stations.

Input

There are multiple test cases. The first line of input contains an integer T indicating the number of test cases. For each test case:

The first line contains an integer N (2 <= N <= 200000).

For the next N - 1 lines, each line contains two integers Xi andYi. That means there is a road connecting building Xi and buildingYi (indexes are 1-based).

Output

For each test case, output three integers. The first one is the minimal longest distance between a building and its nearest fire station. The next two integers are the indexes of the two buildings selected to build the fire stations.

If there are multiple solutions, any one will be acceptable.

Sample Input

241 21 31 451 22 33 44 5

Sample Output

1 1 21 2 4

Author: YU, Xiaoyao; ZHUANG, Junyuan
Source: The 2014 ACM-ICPC Asia Mudanjiang Regional Contest

题意:给一棵树,边权为1,要建两个消防站,一个点到消防站的距离,为它到离它近的消防站的距离。让所有点到消防站的距离的最大值最小。

题解:这题首先要证明消防站一定建在树的直径上。树上的一个点离它最远的点一定是直径的一个端点。假设消防站不在直径上,容易找到一个直径上的点更优(画个图YY)。利用这个性质我们可以二分答案,然后O(n)判断可行性,复杂度O(NlogN)。

具体来说是这样的,假设现在二分的值为x,我们把消防站分别建在离直径端点的距离为x的两个点,然后在bfs判断可行性(即点到消防站的距离的最大值<=x)。当2x小于直径长度的时候,这样做显然是没有问题的。当2*x大于直径长度的时候,显然x还可以更小。所以我们在0到直径长度的一半这个区间二分答案。当2*x等于直径长度的时候,也就是说按照我们放消防站的方法两个消防站建在一个点上,x这个解一定是可行的(我们只关心可行性),我们把其中一个点放到相邻的点就行了。

代码如下:

#include<stdio.h>#include<algorithm>#include<queue>#include<stack>#include<map>#include<set>#include<vector>#include<iostream>#include<string.h>#include<string>#include<math.h>#include<stdlib.h>#define inff 0x3fffffff#define eps 1e-8#define nn 210000#define mod 1000000007typedef long long LL;const LL inf64=LL(inff)*inff;using namespace std;int n;struct node{    int en,next;}E[nn*2];int p[nn],num;queue<int>que;int dis[nn];int Dis[nn];int pre[nn];vector<int>ve;void init(){    memset(p,-1,sizeof(p));    num=0;}void add(int st,int en){    E[num].en=en;    E[num].next=p[st];    p[st]=num++;}void zhijing(){    int i;    memset(dis,-1,sizeof(dis));    dis[1]=0;    que.push(1);    int sta,w;    int zd=1;    while(que.size())    {        sta=que.front();        que.pop();        for(i=p[sta];i+1;i=E[i].next)        {            w=E[i].en;            if(dis[w]==-1)            {                dis[w]=dis[sta]+1;                if(dis[w]>dis[zd])                    zd=w;                que.push(w);            }        }    }    memset(dis,-1,sizeof(dis));    memset(pre,-1,sizeof(pre));    dis[zd]=0;    que.push(zd);    int ix=zd;    while(que.size())    {        sta=que.front();        que.pop();        for(i=p[sta];i+1;i=E[i].next)        {            w=E[i].en;            if(dis[w]==-1)            {                dis[w]=dis[sta]+1;                pre[w]=sta;                if(dis[w]>dis[ix])                    ix=w;                que.push(w);            }        }    }    ve.clear();    while(ix!=-1)    {        ve.push_back(ix);        ix=pre[ix];    }}int fx,fy;int solve(int x){    int lv=ve.size();    fx=ve[x],fy=ve[lv-1-x];    if(fx==fy)        fx=ve[x+1];    memset(dis,-1,sizeof(dis));    int sta,i,w;    dis[fx]=0;    que.push(fx);    while(que.size())    {        sta=que.front();        que.pop();        for(i=p[sta];i+1;i=E[i].next)        {            w=E[i].en;            if(dis[w]==-1)            {                dis[w]=dis[sta]+1;                que.push(w);            }        }    }    memset(Dis,-1,sizeof(Dis));    Dis[fy]=0;    que.push(fy);    while(que.size())    {        sta=que.front();        que.pop();        for(i=p[sta];i+1;i=E[i].next)        {            w=E[i].en;            if(Dis[w]==-1)            {                Dis[w]=Dis[sta]+1;                que.push(w);            }        }    }    int re=-inff;    for(i=1;i<=n;i++)    {        re=max(re,min(dis[i],Dis[i]));    }    return re;}int main(){    int t,u,v,i;    scanf("%d",&t);    while(t--)    {        scanf("%d",&n);        init();        for(i=1;i<n;i++)        {            scanf("%d%d",&u,&v);            add(u,v);            add(v,u);        }        zhijing();        int l=0,r=(ve.size())/2;        int mid;        int ans;        while(l<r)        {            mid=(l+r)/2;            if(solve(mid)<=mid)                r=mid;            else                l=mid+1;        }        ans=solve(l);        printf("%d %d %d\n",ans,fx,fy);    }    return 0;}
上面是我的做法,我在这里贴一下官方题解:

先给出这个问题的数学描述:
给定一棵无根树 T 。定义以 T 上一对点 (a, b) 为“复根”的树的高度 h(a, b) = max min(dist(x, a), dist(x, b)), for all x on T 。现求 H(T) = min h(a, b), for all (a, b) available 。

我们先讨论一下 H(T) 的上界。
引理 1 对任意的一棵树 T ,不妨设其直径长为 d ,有 H(T) <= [d / 2] ([x] 意为对 x 向下取整)。
证明 当 d 为偶数时,T 有且只有一个中心点 x 。取中心点为“复根”,显然 h(x, x) = d / 2 = [d / 2] 。当 d 为奇数时, T 有且只有一条中心边,不妨设其端点分别为 x 和 y 。则显然 h(x, y) = (d - 1) / 2 = [d / 2] 。证毕。

根据引理 1 ,我们可知 H(T) 的可达上界是 [d / 2] 。所以我们接下来的目标就是找是否存在比 [d / 2] 更小的方案。下面我们证明另一个结论。
引理 2 对树 T 上任一对点 (a, b) ,若 h(a, b) < [d / 2] ,则 a 到 b 的路径上一定经过 T 的中心点(边)。
证明 反证法可证。此证明不难,留给读者。

引理 3 对树 T 上任一对点 (a, b) ,若 h(a, b) < [d / 2] ,则对 T 的任一条直径 D ,不妨设距离 a 较近的点为 l ,有 dist(b, l) > [d / 2] 且 dist(a, l) < [d / 2];另一端点 r 距离 b 较近,有 dist(b, r) < [d / 2] 且 dist(a, r) > [d / 2] 。
证明 由引理 2 可以直接推出该引理。此证明不难,留给读者。

引理 4 对任意的 (a, b) ,若其中存在至少一个点不在树 T 的某一条直径上,且 h(a, b) < [d / 2] 。则存在一对点 (a', b') ,其中 a' 和 b' 都在树 T 的任一条直径上,使得 h(a', b') <= h(a, b) 。
证明 对任意满足条件的 (a, b) ,不妨设其中 b 不在 T 的一条直径 D 上。记:
x: 直径 D 上距离 b 最近的点为 x 。
T': 将 T 上 D 包含的边都从边集中去掉得到一个森林 G ,其中 x 所在的树记为 T' 。
y: 在 T' 上 x 的最远点记为 y 。
l, r: 由于 h(a, b) < [d / 2] ,故 D 的两个端点中记距离 a 较近的点为 l ,另一端点记为 r 。接下来证明 h(a, x) <= h(a, b) :
根 据以上条件,显然有:dist(x, r) < dist(b, r) < [d / 2] 。进而可得 x 到 r 的路径上不经过 T 的中心点(边)。于是可知 dist(x, r) >= dist(x, y) (否则 r 就不可能在直径上)。
1) 因为 h(a, b) >= min(dist(a, r), dist(b, r)) = dist(b, r) > dist(x, r) >= dist(x, y) ,且 y 是 T' 中 x 的最远点。所以对 T' 中的任一点 k ,都有 dist(x, k) <= dist(x, y) <= h(a, b) ,故 min(dist(a, k), dist(x, k)) <= h(a, b) 。
2) 而对于所有不在 T' 中的点 k ,都有 dist(x, k) <= dist(b, k) ,故 min(dist(a, k), dist(x, k)) <= min(dist(a, k), dist(b, k)) <= h(a, b) 。
结合 1) 和 2) 可得:对 T 中任一点 k , 都有 min(dist(a, k), dist(x, k)) <= h(a, b) 。故 h(a, x) <= h(a, b) 。
所以我们找到了一对点 (a, x) ,满足 h(a, x) <= h(a, b) 。证毕。

于是我们得到了下面这个最关键的结论:
定理 对树 T 上任一对点 (a, b) ,若 h(a, b) = H(T) ,则对 T 的任一条直径 D , a 和 b 都在 D 上。
证明 由引理 3 可知,不存在一种有一个点不在直径上的最优解。故该定理得证。

所以我们只要枚举所有两个点都在直径上的方案就可以找到一组最优解。于是我们就转化为一个一维的问题。可以二分答案,然后 O(N) 的判定可行性。总体复杂度是 O(NlogN) 。


看了看别人的题解,还有O(n)的做法,不过我不太确定正确性,贴一下。

其实这两个点一定是树直径上的两个点,如果能想到这个就很好搞了,只需要求出树的直径,然后从中间位置把树拆成两颗子树,然后分别求出子树的中心就好了。

证明如下,假如我们已经求出树上的两个点u, v满足条件。那么,我们从u, v中间把树拆开肯定是最优的,这个很容易想到。然后,对于u点,肯定在原树的直径上,因为假如u点没有在直径上的话,u点一定是在子树的一个分支上,而且到树直径端点的距离小于等于u到分支端点的距离,这样的话分支定可以取代原来直径上的一个端点而成为直径,u在直径上,与假设矛盾。所以u一定在直径上,同理v也一定在直径上。然后就搞出来了==




0 0
原创粉丝点击