• [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]);
    }
     
     
     
    
  • 相关阅读:
    知识【inline】
    .net实现文件或目录复制到指定目录 及 压缩
    asp实现页面打印功能
    C#创建Windows服务(附服务安装)
    导出合并行及合并列
    Abp添加DBContext
    Background Jobs 调用接口时间长解决
    DataTable去掉空行
    Maven配置
    二维码q
  • 原文地址:https://www.cnblogs.com/chasedeath/p/11825353.html
Copyright © 2020-2023  润新知