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;

}

 

你可能感兴趣的:(Inherit)