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;
}

 

posted @ 2013-01-11 17:04  yefeng1627  阅读(340)  评论(1编辑  收藏  举报

Launch CodeCogs Equation Editor