1. 程式人生 > >BZOJ 1787/ 洛谷 4281 [AHOI2008] Meet 緊急集合

BZOJ 1787/ 洛谷 4281 [AHOI2008] Meet 緊急集合

題目描述

歡樂島上有個非常好玩的遊戲,叫做“緊急集合”。在島上分散有N個等待點,有N-1條道路連線著它們,每一條道路都連線某兩個等待點,且通過這些道路可以走遍所有的等待點,通過道路從一個點到另一個點要花費一個遊戲幣。

參加遊戲的人三人一組,開始的時候,所有人員均任意分散在各個等待點上(每個點同時允許多個人等待),每個人均帶有足夠多的遊戲幣(用於支付使用道路的花費)、地圖(標明等待點之間道路連線的情況)以及對話機(用於和同組的成員聯絡)。當集合號吹響後,每組成員之間迅速聯絡,瞭解到自己組所有成員所在的等待點後,迅速在N個等待點中確定一個集結點,組內所有成員將在該集合點集合,集合所用花費最少的組將是遊戲的贏家。

小可可和他的朋友邀請你一起參加這個遊戲,由你來選擇集合點,聰明的你能夠完成這個任務,幫助小可可贏得遊戲嗎?

輸入輸出格式

輸入格式:

 

第一行兩個正整數N和M(N<=500000,M<=500000),之間用一個空格隔開。分別表示等待點的個數(等待點也從1到N進行編號)和獲獎所需要完成集合的次數。 隨後有N-1行,每行用兩個正整數A和B,之間用一個空格隔開,表示編號為A和編號為B的等待點之間有一條路。 接著還有M行,每行用三個正整數表示某次集合前小可可、小可可的朋友以及你所在等待點的編號。

 

輸出格式:

 

一共有M行,每行兩個數P,C,用一個空格隔開。其中第i行表示第i次集合點選擇在編號為P的等待點,集合總共的花費是C個遊戲幣。

 

輸入輸出樣例

輸入樣例#1:

6 4  
1 2  
2 3  
2 4 
4 5
5 6
4 5 6
6 3 1
2 4 4 
6 6 6

輸出樣例#1:

5 2
2 5
4 1
6 0


說明

40%的資料中N<=2000,M<=2000
100%的資料中,N<=500000,M<=500000

 

很顯然這是一道LCA的題目,但是每次要求的點數從2個增加到了3個。

既然要求出最小的花費,那麼所選的集合點肯定要使到給出的3個點的距離儘量短,且"重複路徑"也要儘量少。

什麼是"重複路徑"呢?就比如下面這張圖。

                                                                 

假如我們要求的是3,4,6這3個點的集合點。顯而易見選4是最優的。

經過一點點思考可以發現,所求的集合點必定在這三個點所構成的鏈上。

而到這條路鏈上的點的花費的區別就是這個"重複路徑"。這3個點走到4是沒有重複路徑的。

而如果走到2的話4->2這條路徑就會被經過兩次,這樣顯然花費就更高了。

然後我們需要求出這個最優的點。還是上面那張圖,我直接複製下來方便看吧。

                                                                      

通過直接觀察可得,3和4的LCA是2,3和6的LCA也是2,4和6的LCA是4。

更進一步,3個點兩兩的LCA必定有兩個是相同的。且3個點的分佈只有兩種情況。

1.三個點在同一側,就比如上圖中的4,5,6。這種情況的最優集合點顯然是中間那個點。

2.兩個點在一側,剩下一個點在另一側。上圖中的3,4,6。這種情況為了使得重複路徑最少,我們所選的點要儘量與那兩個在同一側的點近。當然不能比較淺的點那個點更深了(上圖中我們選的最深的點不能比點4更深,不然就會多出一條重複路徑)。這樣我們選的點就肯定是在同一側的那兩個點的LCA了(上圖中就是點4了)。

而確定這個點很簡單。之前我說過有兩個LCA是必定相同的。單獨在一側的點和另外兩個在另一側的點的LCA就是相同的(LCA(3,4)=LCA(3,6))。我們要選取的就是另一個LCA了。雖然這是情況2的解,但是情況1只是情況2的一種特殊形式,所以就是通解了。

要選的點求出來了那麼就只剩下計算花費了。比較簡單的方法就是對這3個點和所選的集合點的深度做差再取絕對值最後加起來,這樣是很麻煩的。

                                                   

假設給出的點分別是a,b,c。k1&k2是LCA(a,b)和LCA(a,c)。k3是LCA(b,c)。我們記錄了每個點的深度Depth[i]。根據我上面的分析,我們所選的集合點為k3。三個點到k3的距離為Depth[a]+Depth[b]+Depth[c]-3*Depth[k3]+2*Depth[k3]-Depth[k1]-Depth[k2]。化簡一下就是Depth[a]+Depth[b]+Depth[c]-Depth[k1]-Depth[k2]-Depth[k3]。

下面貼上程式碼,我用倍增寫的LCA。如果程式碼炸了我這裡還放了一份

#include<cstdio>
#include<iostream>
#include<cmath>
#include<algorithm>
#define MAXN 500005
using namespace std;
struct edge
{
    int next,node;
}h[MAXN*2];//鄰接表存圖
int n,q,tot=0;
int Head[MAXN],Depth[MAXN],p[MAXN][32];//Head是上面存圖裡的頭邊。Depth存每個點的深度
inline int read()//p[i][j]表示點i向上跳2^j到的點
{
    int sum=0,t=1;char ch;ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-') t=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){sum=sum*10+ch-'0';ch=getchar();}
    return sum*t;
}//快讀
inline void add(int u,int v)
{
    h[++tot].next=Head[u];
    h[tot].node=v;
    Head[u]=tot;
}//加邊
inline void dfs(int pos)
{
    for(register int i=Head[pos];i;i=h[i].next)
    {
        int v=h[i].node;
        if(p[pos][0]!=v)
        {
            p[v][0]=pos;
            Depth[v]=Depth[pos]+1;
            dfs(v);
        }
    }
}//搜尋跑一遍收集圖的資訊
inline int lca(int a,int b)
{
    if(Depth[a]>Depth[b]) swap(a,b);
    int delta=Depth[b]-Depth[a];
    for(register int i=0;(1<<i)<=delta;++i)
        if((1<<i)&delta)
            b=p[b][i];
    if(a!=b)
    {
        for(register int i=(int)log2(n);i>=0;--i)
            if(p[a][i]!=p[b][i])
            {
                a=p[a][i];
                b=p[b][i];
            }
        a=p[a][0];
    }
    return a;
}//倍增求LCA
int main()
{
    int x,y,z;
    n=read();
    q=read();
    for(register int i=1;i<n;++i)
    {
        x=read();
        y=read();
        add(x,y);
        add(y,x);
    }
    for(register int i=1;i<=n;++i)
        if(!p[i][0])
        {
            Depth[i]=1;
            p[i][0]=i;
            dfs(i);
        }//預設點1為根節點。這樣寫防止圖不聯通。
    for(register int j=1;(1<<j)<=n;++j)
        for(register int i=1;i<=n;++i)
            p[i][j]=p[p[i][j-1]][j-1];//處理倍增。
    for(register int i=1;i<=q;++i)
    {
        int ans;
        x=read();
        y=read();
        z=read();
        int k1=lca(x,y),k2=lca(x,z),k3=lca(y,z);
        if(k1==k2) ans=k3;
        else if(k2==k3) ans=k1;
        else if(k1==k3) ans=k2;
        printf("%d %d\n",ans,Depth[x]+Depth[y]+Depth[z]-Depth[k1]-Depth[k2]-Depth[k3]);
    }
    return 0;
}