洛谷P4281【AHOI2008】緊急集合/聚會
這道題目有兩個版本的題面(
),不過題目的意思是一樣的。
這道題給出了一棵
個點的樹和
個詢問,對於每個詢問給出三個點,求這棵樹上到這三個點距離之和最小的點以及最小距離和。
我們先來考慮一個簡化版本:每次不是詢問三個點,而是兩個。那麼答案顯而易見:合法的點為這兩個點的 到這兩個點路徑上的任意一點,最短距離和也就是這兩個點的距離。
好了,那麼我們回到這個問題,給你三個點怎麼辦。根據我們剛剛得出的結論,答案就在這三個 到這三個點的路徑上。而 有一個性質:對樹上任意三個點兩兩求 ,這三個 至少有兩個相同,且如果有兩個 相同,另外一個 的深度一定是最大的。
我們設詢問的三個點分別為 、 、 , 為 , 和 均為 。那麼答案所求的點一定是 。為什麼呢?首先我們不難發現, 一定在 到 和 的路徑上。令 表示將 作為集合點時的距離和, 表示點 的深度,那麼顯然 到 的距離 到 的距離 我們剛剛說過答案點在 到 、 、 的路徑上,那麼我們分類討論一下:
1、如果答案點 在 到 的路徑上,那麼距離和為 ,對比上面 的式子可以發現這個距離和一定大於 ,因為顯然 。
2、如果答案點 在 到 或 的路徑上,那麼距離和為 ,對比上面的式子,因為 ,所以這個距離和也大於 。
現在我們證明了答案一定為 。如果我們再觀察一下 的式子可以發現,因為 既為 ,又為 ,所以 可以轉換成 ,這樣就免去了一個分類討論。
另外就是這道題的資料範圍比較大,推薦樹剖求 ,倍增的話可能需要卡一下常數(至少卡常之後可以在洛谷過去)
倍增:
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=500010;
int n,m,top,h[N],to[N<<1],pre[N<<1],d[N],fa[N][22];
int read()//快讀
{
int sum=0;char ch=getchar();
while(ch<'0'||ch>'9')ch=getchar();
while(ch>='0'&&ch<='9')sum=(sum<<3)+(sum<<1)+ch-'0',ch=getchar();
return sum;
}
void ins(int u,int v)//建邊
{
pre[++top]=h[u];h[u]=top;to[top]=v;
}
void dfs(int x)
{
for(int i=h[x];i;i=pre[i])
{
int v=to