[BZOJ2071] [POI2004]JAS

[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]);
}
 
 
 
posted @ 2019-11-09 13:22  chasedeath  阅读(269)  评论(0编辑  收藏  举报