「AHOI2008」「LuoguP4281」緊急集合 / 聚會(LCA
題目描述
歡樂島上有個非常好玩的遊戲,叫做“緊急集合”。在島上分散有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
題解
對於每次詢問,設三個點分別為x1,x2,x3。
兩兩求lca,得到三個lca,其中最深的那個點為最優集合點。
不會嚴謹證明,畫畫圖感性理解還是不難的。
然後有了集合點,問題轉化為求樹上兩點間的距離。
隨便搞搞就出來了。
1 /* 2 qwerta 3 P4281 [AHOI2008]緊急集合 / 聚會 4 Accepted 5 100 6 代碼 C++,2.24KB 7 提交時間 2018-10-09 18:50:02 8 耗時/內存 9 1148ms, 24100KB 10 */ 11 #include<iostream> 12 #include<cstdio> 13 using namespace std; 14 inline int read() 15 { 16 char ch=getchar(); 17 int x=0; 18 while(!isdigit(ch))ch=getchar(); 19 while(isdigit(ch)){x=x*10+ch-‘0‘;ch=getchar();} 20 return x; 21 } 22 const int MAXN=500003; 23 struct emm{ 24 int e,f; 25 }a[2*MAXN]; 26 int h[MAXN]; 27 int fa[MAXN],top[MAXN],d[MAXN],siz[MAXN],z[MAXN]; 28 void dfs(int x) 29 { 30 siz[x]=1,top[x]=x; 31 int mac=0,macc=0; 32 for(int i=h[x];i;i=a[i].f) 33 if(!d[a[i].e]) 34 { 35 d[a[i].e]=d[x]+1; 36 fa[a[i].e]=x; 37 dfs(a[i].e); 38 siz[x]+=siz[a[i].e]; 39 if(siz[a[i].e]>macc){mac=a[i].e,macc=siz[a[i].e];} 40 } 41 z[x]=mac,top[mac]=x; 42 return; 43 } 44 int q[MAXN],dfn[MAXN]; 45 int tot=0; 46 void dfss(int x) 47 { 48 q[++tot]=x; 49 dfn[x]=tot; 50 if(z[x])dfss(z[x]); 51 for(int i=h[x];i;i=a[i].f) 52 if(fa[a[i].e]==x&&a[i].e!=z[x]) 53 dfss(a[i].e); 54 return; 55 } 56 int fitop(int x) 57 { 58 if(top[x]==x)return x; 59 return top[x]=fitop(top[x]); 60 } 61 int lca(int u,int v)//樹剖求lca 62 { 63 while(top[u]!=top[v]) 64 { 65 if(d[top[u]]<d[top[v]])swap(u,v); 66 u=fa[top[u]]; 67 } 68 if(d[u]<d[v])swap(u,v); 69 return v; 70 } 71 int dmin(int x1,int x2,int x3)//找深度最深的點 72 { 73 if(d[x1]>=d[x2]&&d[x1]>=d[x3])return x1; 74 if(d[x2]>=d[x1]&&d[x2]>=d[x3])return x2; 75 return x3; 76 } 77 int dis(int u,int v)//求距離 78 { 79 int ans=0; 80 while(top[u]!=top[v]) 81 { 82 if(d[top[u]]<d[top[v]])swap(u,v); 83 ans+=dfn[u]-dfn[top[u]]+1; 84 u=fa[top[u]]; 85 } 86 if(d[u]<d[v])swap(u,v); 87 ans+=dfn[u]-dfn[v]; 88 return ans; 89 } 90 void write(int x) 91 { 92 if(x>9)write(x/10); 93 putchar(x%10+‘0‘); 94 return; 95 } 96 int main() 97 { 98 //freopen("a.in","r",stdin); 99 int n=read(),m=read(); 100 tot=0; 101 for(int i=1;i<n;++i) 102 { 103 int u=read(),v=read(); 104 a[++tot].f=h[u]; 105 h[u]=tot; 106 a[tot].e=v; 107 a[++tot].f=h[v]; 108 h[v]=tot; 109 a[tot].e=u; 110 } 111 int s=min(n,7);//幸運數賽高!(逃 112 d[s]=1; 113 dfs(s); 114 tot=0; 115 dfss(s); 116 for(int i=1;i<=n;++i) 117 top[i]=fitop(i); 118 for(int i=1;i<=m;++i) 119 { 120 int x1=read(),x2=read(),x3=read(); 121 int l1=lca(x1,x2),l2=lca(x2,x3),l3=lca(x1,x3);//兩兩取lca 122 int p=dmin(l1,l2,l3);//取最深的為集合點 123 int ans=dis(x1,p)+dis(x2,p)+dis(x3,p);//算距離,加起來 124 write(p); 125 putchar(‘ ‘); 126 write(ans); 127 putchar(‘\n‘);//輸出 128 } 129 return 0; 130 }
「AHOI2008」「LuoguP4281」緊急集合 / 聚會(LCA