[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
#include
#include
#include
#include
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){ ((ab)&&(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<=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<>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]);
}
 
 
 

你可能感兴趣的:([BZOJ2071] [POI2004]JAS)