[BZOJ2071] [POI2004]JAS
题目描述
在Byteotia有一个洞穴. 它包含n 个洞室和一些隧道连接他们. 每个洞室之间只有一条唯一的路径连接他们. Hansel 在其中一个洞室藏了宝藏, 但是它不会说出它在哪. Gretel 想知道. 当她询问一个洞室是否有宝藏时,如果她猜对了Hansel 会告诉她,如果猜错了他会告诉她哪个方向会有宝藏. 给出洞穴的信息,那么无论Hansel 把宝藏藏在了哪,求出最少要询问多少次才能找到宝藏.
输入格式
输入一个数n, 1<= n <= 50,000. 表示洞室总数,接下来n-1 行描述n – 1条边.
输出格式
输出一个数表示最少询问次数.
样例
样例输入
5
1 2
2 3
4 3
5 3
样例输出
2
提供一下这篇题解的主要内容
1.将原问题转化为一个树上标号问题,标号即最优决策时的询问顺序。标号满足:
任意两个相同标号点之间必然有一个点标号大于它,对应先访问该点
标号对应的访问顺序即:对于任意一个联通块,每次都选取其中标号最大的节点访问,然后将联通块分成几部分重复操作
答案即最大标号的最小值
存在一种可行的标号方式,就是每次取重心标号,这样能保证答案在\(log n\)以下,但并不一定是最优解
2.考虑对于标号最优的贪心
编号最多只\(logn\),所以可以状压
我们按照子树贪心,每棵子树维护一个\(S[i]\),表示子树内存在的标号,并且这些标号到\(i\)的路径上没有更大的点
对于\(u\)收集子树时,如果有多棵子树包含这种标号\(i\),那么\(u\)的标号必须\(>i\),同时,子树里出现的标号\(u\)不能取
由于最大编号不一定在根节点处取到,所以还要同步维护一个最大值
#include<cstdio>
#include<cctype>
#include<algorithm>
#include<cstring>
#include<iostream>
using namespace std;
#define reg register
typedef long long ll;
#define rep(i,a,b) for(reg int i=a,i##end=b;i<=i##end;++i)
#define drep(i,a,b) for(reg int i=a,i##end=b;i>=i##end;--i)
inline void cmax(int &a,int b){ ((a<b)&&(a=b));}
inline void cmin(int &a,int b){ ((a>b)&&(a=b));}
char IO;
int rd(){
int s=0,f=0;
while(!isdigit(IO=getchar())) f|=(IO=='-');
do s=(s<<1)+(s<<3)+(IO^'0');
while(isdigit(IO=getchar()));
return f?-s:s;
}
const int N=1e5+10;
int n;
struct Edge{
int to,nxt;
} e[N<<1];
int head[N],ecnt;
void AddEdge(int u,int v) {
e[++ecnt]=(Edge){v,head[u]};
head[u]=ecnt;
}
#define erep(u,i) for(int i=head[u];i;i=e[i].nxt)
int Log[N],dp[N],S[N];
void dfs(int u,int f) {
int cnt[20];
memset(cnt,0,sizeof cnt);
erep(u,i) {
int v=e[i].to;
if(v==f) continue;
dfs(v,u);
rep(j,0,18) if(S[v]&(1<<j)) cnt[j]++;
cmax(dp[u],dp[v]);
S[u]|=S[v];
}
int mk[20];
memset(mk,0,sizeof mk);
rep(i,0,18) if(S[u]&(1<<i)) mk[i]=1; //子树里出现的都不能取
drep(i,18,0) if(cnt[i]>=2) {
rep(j,0,i) mk[j]=1;
break;
}// 如果子树里存在两次i,那么i一下均不能取
int t;
rep(i,0,18) if(!mk[i]){ t=i; break; }
rep(i,0,t-1) if(S[u]&(1<<i)) S[u]^=1<<i;
S[u]|=1<<t;//已经取了t,不用考虑t以下的值
cmax(dp[u],t);
}
int main(){
n=rd();
Log[0]=-1;
rep(i,2,n) Log[i]=Log[i>>1]+1;
rep(i,2,n) {
int u=rd(),v=rd();
AddEdge(u,v);
AddEdge(v,u);
}
dfs(1,0);
printf("%d\n",dp[1]);
}