• poj 2414 Phylogenetic Trees Inherited 完全二叉树 状态压缩位运算模拟集合操作 动态规划


      题目题意异常恶心,难以理解......

      呵呵.....不过题意是 Lyush大神 花费一小时看明白后透露给笔者的, 到现在依然YM此大婶当中......

      先说说题目大意:

      

         一颗完全二叉树,有 N ( n <= 1024,且必定为2的整数幂,意味着是一颗完全二叉树 ) 个叶子节点, 

    每一个节点都含有一个长度为 L L ( L <= 1000 ) 的串 ( 串仅由大写字母构成), 现仅仅知道N个叶子节点串的组成.

    其他节点串不知道.

         但我们知道,直接父节点相同的两个子节点,其对应位置不同则花费为1. 整棵树花费最小.

         让你求,整棵树的最小花费, 以及根节点的可能串元素组成.若有多种情况则随意输出一种即可. ( Special Judge )

      

      解题思路:

         第一点我们知道每一个串中不同位置间无约束条件. 所以我们可以分别处理.

           定义函数

            T( rt ) 表示以 rt 为根节点的二叉树最小花费值

            A( rt ) 表示以 rt 为根节点的二叉树最小花费值时, 可选取的字符的集合

           那么,若对于 有相同直接父节点 rt 的节点 lch, rch 已得出 T( lch ), A( lch ) 与 T( rch ), A( rch) 

         那么对于 其父节点 rt 来看, 若 T( rt ) 要取得最优解, 则

               or                   //这里的并与交,指代集合的操作

    对于这个结论,我们可以通过归纳法证明:

         有三种情况: 

            一, 当  , 则 lch,rch位置字符与rt仅有一个不同 花费 cost = 1

            二, 当 ,  则 lch,rch位置字符与rt都相同 花费 cost = 0

            三, 当, 则lch,rch位置字符与rt两两间都不同,花费 cost = 2

         所以我们可以得出结论.

            当且仅当, , 此时其父节点rt, , 并且此时花费 cost = 0

             

              否则, 此时 , 其父节点rt, , 并且此时 子节点 lch, rch 中必有一个与根节点不同,所以花费cost = 1

    换句话说,我们每次合并集合,则花费cost必定+1

         

         关于编码技巧问题, 这里我们除了将不同位置分开处理之外, 还可以观察到, 串元素仅有大写字母A-Z组成,共26个.属于int范围内.

    因为可选项最多26个,若我们用数组来模拟集合,则势必多一层26的循环,时间复杂度增大不少, 我们可以使用 位运算,状态压缩到一个 int 值当中,

    这样集合的 交与并 操作则可以使用 位运算的 &与| 来替代. 

    AC代码:

    View Code
    #include<stdio.h>
    #include<string.h>
    #include<stdlib.h>
    
    char seq[1024][1024];
    int T[2048], n, m;
    
    int main()
    {
        while( scanf("%d%d", &n,&m) , n+m )
        {
            for(int i = 0; i < n; i++)
                scanf("%s", seq[i]);
    
            int cost = 0;    
            for(int p = 0; p < m; p++) 
            {    
                
                for(int x = 0; x < n; x++)
                    T[x+n] = 1<<(seq[x][p]-'A');
                for(int rt = n-1; rt >= 1; rt-- )
                {
                    int lch = rt<<1, rch = rt<<1|1;
                    T[rt] = T[lch]&T[rch];
                    if( T[rt] == 0 )
                    {
                        T[rt] = T[lch] | T[rch];
                        cost++;
                    }    
                }
                char ch = 'A';
                while( (T[1]&1) == 0 )
                {
                    ch++;
                    T[1] >>= 1;
                }    
                printf("%c", ch );
            }    
            printf(" %d\n", cost );    
        }
        return 0;
    }

    再贴个搓代码, 前面直接模拟的写法,外加数组模拟集合操作,时间+空间一起T..虽然不好,总也是自己想了好久的......

    View Code
    #include<stdio.h>
    #include<string.h>
    #include<stdlib.h>
    #include<math.h>
    const int inf = 0x3fffffff;
    typedef long long LL;
    #define MIN(a,b) (a)<(b)?(a):(b)
    struct Node
    {
        bool num[1001][27]; // 27001 Byte
        char s[1001];        // 1001 Byte
    }node[1100<<1];            // 2200     5*10^7 B
    
    int n, m, mf;
    LL ans;
    
    void print_n( int rt )
    {
        printf("rt = %d\n", rt );    
        for(int p = 0; p < m; p++)
        {
                printf("p=%d : ", p);    
                for(int op = 0; op < 26; op++)
                {
                    printf("%d ", node[rt].num[p][op] );    
                }
                printf("\n\n");    
        }
    }
    void print_s( int rt )
    {
        printf("rt = %d:  ",rt);    
        for(int p = 0; p < m; p++)
            printf("%d ", node[rt].s[p] ); puts("");
    }
    void init()
    {
            //初始化最后一层可选项    
            for(int i = n; i < n+n; i++ )
            {
                memset( node[i].num, 0, sizeof( node[i].num ) );
                for(int p = 0; p < m; p++)
                    node[i].num[p][ node[i].s[p]-'A' ] = true;    
            }    
            // 从下层 log2(n)+1 往上更新固定值,或单一固定值
            // 对于无法确定的则保存下当前可选择项    
            // 预处理最后一层与上一层的关系    
        
            for(int f = mf; f > 1; f--  )
            {
                //第f层有 (2^(f-1)) 个节点, 且从 (2^(f-1))开始
                int x = 1<<(f-1);    
                for(int lch = x; lch < x+x; lch += 2 )
                {
                    int rch = lch+1, fa = lch/2;
                    //printf("fa = %d, lch = %d, rch = %d\n", fa, lch, rch );
                    //初始化父节点fa,可选项    
                    memset( node[fa].num, 0, sizeof(node[fa].num) );
                    node[fa].s[m] = '\0'; //对每个构造串手动添加字符串结尾标志
    
                    // 处理每个位置p, 位置总长m
                    for(int p = 0; p < m; p++)
                    {
                        //printf("l.s=%d,r.s=%d\n",node[lch].s[p],node[rch].s[p] );    
                        if( (node[lch].s[p] == 0) || (node[rch].s[p] == 0) )
                        {    node[fa].s[p] = 0;    }
                        else{ 
                            //左右子节点 串p 位置都为固定值
                            if( node[lch].s[p] != node[rch].s[p] )
                                node[fa].s[p] = 0;
                            else    
                                node[fa].s[p] = node[lch].s[p];        
                        }    
                        for(int ch = 0; ch < 26; ch++ )
                            node[fa].num[p][ch] = (node[lch].num[p][ch] || node[rch].num[p][ch]);    
                    }
            //        print_s(fa);    
                }
            }    
    }
    // 以rt为根节点的树, 其父节点选择fa_op的最小花费 
    int dfs( int p, int rt, int fa_op )
    {
        int floor = (int)(log2(1.*rt))+1; //目前层数
        int fa = rt/2;
    
        bool flag = false; // 标记值,判定节点rt是否需要选择,默认为不需要
        // 若节点rt, p位置字符固定,则该节点下面的子树该位置都相同,无需继续选择
        // 因为mf层,都没确定值,则当递归到mf层时,必定开始回溯.    
        if( node[rt].s[p] != 0 )
        {
            if( node[rt].s[p]-'A' == fa_op ) return 0;
            else    return 1;
        }
        else
        {
            //若不确定,则分为两种情形:
            // 一,当节点rt,包含其父节点fa_op选项时选择,rt同时选择该选项
            // 二,当节点rt,不包含其父节点fa_op选项时,rt开始枚举选择最优选项 .并且此时导致 花费+1
            LL c = 0;    
            if( node[rt].num[p][ fa_op ] == true )
            {
                c += dfs( p, rt<<1, fa_op );
                c += dfs( p, rt<<1|1, fa_op);
                return c;
            }
            else
            {
                // 此时应在所有可选项中取一个最小值
                c = inf;
                for(int op = 0; op < 26; op++)
                {
                    LL tmp = 0;    
                    if( node[rt].num[p][op] == true )
                    {
                        //若当前op可选
                        tmp += dfs( p, rt<<1, op );
                        tmp += dfs( p, rt<<1|1, op );
                        c = MIN( c, tmp );    
                    }
                }
                return c+1; //因为此位置与父节点不想符合,造成花费额外+1    
            }    
        }    
    }
    void solve(){
    
        ans = 0; // 总花费
        // 确定位置P 最小花费cost,以及最佳选项
        for(int p = 0; p < m; p++)
        {
            // 若p位置字符不确定,则进行搜索    
        //    printf("node[1].s[%d] = %d\n",p, node[1].s[p] );    
        //    printf("%d ", node[1].s[p] );    
            if( node[1].s[p] == 0 )
            {
                LL cost = inf;
                int ch = -1; 
            
                for(int op = 0; op < 26; op++)
                {
                    //printf(" node[%d].num[%d][%d] = %d\n", 1, p, op, node[1].num[p][op] );    
                    if( node[1].num[p][op] ) //当前字符可选    
                    {
                        LL c = 0;    // 初始化 当选择OP选项时,最小花费
                    
                        //假定 父串p位置取op选项,然后递归处理下一层
    
                        c += dfs( p, 2, op ); //位置p, 子树根2, 父节点选项op 
                        c += dfs( p, 3, op );    
            //            printf("c = %lld\n", c );
                        if( cost > c ) //取最小花费c,所在选项op
                        {
                            cost = c; 
                            ch = op;
                        }
                    }    
                }    
                //确定p位置字符为 ch, 最小花费为 cost;
                ans += cost;
                node[1].s[p] = ch+'A';
            //    printf("ch = %d, cost = %lld\n", ch, cost );    
            }
        }
        //输出
        printf("%s %lld\n", node[1].s, ans );
    }
    int main()
    {
        freopen("8.in","r", stdin);
        freopen("8.out","w",stdout);
        while( scanf("%d%d", &n,&m) , n+m )
        {
            mf = (int)(log2(1.*n))+1;
            
            //printf("mf = %d\n", mf );
            for(int i = n; i < n+n; i++)
                scanf("%s", node[i].s );    
    
            // 由下而上更新 可确定项,以及生成可选项
            init();
        
            // 由上而下搜索位置p 可选项取最小值    
            solve();    
        }
        return 0;
    }
  • 相关阅读:
    solidworks 学习 (二)洗手液瓶
    solidworks 学习 (一)螺丝刀
    tensorflow 2.0 学习(三)MNIST训练
    tensorflow 2.0 学习(二)线性回归问题
    tensorflow 2.0 学习(一)准备
    sscanf linux-c从一个字符串中读进与指定格式相符的数据
    Linux-c glib库hash表GHashTable介绍
    Linux-c给线程取名字
    linux-c getopt()参数处理函数
    golang Linux下编译环境搭建
  • 原文地址:https://www.cnblogs.com/yefeng1627/p/2856723.html
Copyright © 2020-2023  润新知