Google Code Jam Round2 题解报告 Problem A.Cheating a Boolean Tree

作为一个算法的不入门兴趣者,玩着参加了下Google Code Jam,如预料的在第二轮被淘汰,不过事后分析其实也不是没有希望了,只需要20分就可以晋级哦。

下面发发我事后研究的题解报告。

本来好久没来,也不想发这里,可是另外一个blog说字数超过限制,所以嘛……

闲话少说,下面发题解了,大家轻拍。代码参考了下ACRUSH的,高手就是写得比我好看得多。

A.    Cheating a Boolean Tree

题意描述

在这个题目中,我们将考虑一种叫做布尔树(boolean tree)的二叉树。在这种树种,每一层都是完整的,除了最下面一层,在最下层中,所有结点都尽可能地靠近左边。此外,树中每个节点都拥有0个或2个子结点。

布尔树特殊之处在于每个节点都拥有一个值,10。此外,每个内部结点(非叶子结点)都有一个“与门”或“或门”。内部结点的取值是通过其两个子结点进行对应的“与”或“或”运算的值。所有叶结点的值是通过输入获得,因此所有结点的值都可以通过计算获得。

树的根结点是我们特别关注的。我们希望根结点的取值是指定的值V1或者0。不幸的是,往往根结点实际的值并不是我们所希望的。然而幸运的是,我们可以通过改变某些结点的门来得到希望的取值。

给定一棵布尔树和树中可以改变门的结点,找出需要改变最少多少个门才可以得到想要的取值,如果不能得到值,则输出”IMPOSSIBLE”

输入

输入的第一行包含测试数据的组数,N。后面跟着N组测试数据。

每组数据第一行有两个数MVM指示这棵树有多少结点,它能够保证每个结点都有0个或2个子结点。V表示希望的根结点取值,01

接下来M行描述树中的节点,第X行描述第X个结点。

(M-1)/2行描述内部结点。每行有两个数GC,每个取值都是01G表示结点的门,1代表与门,0表示或门。C表示这个结点的门是否可以改变,0表示不能改变,1表示可以变动。

接下来(M+1)/2行描述叶子结点。每行包含一个数I01,叶结点的值。

下图中是个实例:

输出

每组测试数据输出下面的格式:

Case #X: Y

X指第几组,Y表示最少要变动几个门,如果不能得到需要的值,则YIMPOSSIBLE

限制

1<N<=20

小测试数据:

最多30个结点

大测试数据:

最多10000个结点。

示例

Input

 

2

9 1

1 0

1 1

1 1

0 0

1

0

1

0

1

5 0

1 1

0 0

1

1

0

  

Output

  

Case #1: 1

Case #2: IMPOSSIBLE

 

 

解题思路

总的说来,这道题的本质是考递归的应用,同时需要考虑对于中间状态的保存。

#include <cstdio>

#include <iostream>

using namespace std;

 

//比较ab,如果ba小,则将b的值赋给a

//注意a是引用的

template<class T> inline void checkmin(T &a, T b)

{

     if(b<a) a=b;

}

 

//指示不可能

const int impossible = 100000;

//数组的最大数目

const int maxsize = 40000;

 

int n;   //结点数目

int f[maxsize][2]; //用于保存第x个结点要取某值需要改变多少次的中间结果

//数组第一个是结点的序号,第二个则是表示取值是或

int G[maxsize], C[maxsize], X[maxsize];

//分别保存各结点的门,可否改变,取值

 

//计算结点的值

//x,y是两子结点的值,op是门的种类

int calc(int x, int y, int op)

{

     if(op == 1)

         return x & y;

     else

         return x | y;

}

 

//递归计算要改变的数目

//id是第几结点,exp是值希望该结点是何值

int Solve(int id, int exp)

{

     //子结点时

     if(id > (n-1)/2)

         //如果结点的值等于希望值则返回,否则返回

         return (exp == X[id]) ? 0:impossible;

    

     int &ret = f[id][exp];

     if(ret != -1) //如果取值不为-1,则返回数组中的值

         return ret;

    

     ret = impossible;

     //遍历子结点的所有可能

     for(int left = 0; left < 2; left++)

     {

         for(int right = 0; right < 2; right++)

         {

              //如果计算等于希望的值,则分别计算两个子结点需要改变的次数

              //并与ret比较,将小的赋值给ret

              if(calc(left, right, G[id]) == exp)

                   checkmin(ret, Solve(id*2,left)+Solve(id*2+1,right));

              //如果该结点可以改变则计算改变门后的情况,与上类似

              if(C[id] == 1 && calc(left,right, 1-G[id])==exp)

              {

                   checkmin(ret, Solve(id*2,left) + Solve(id*2+1,right)+1);

              }

         }

     }

     return ret;

}

 

int main()

{

     FILE *in;

     FILE *out;

    

     in = fopen("A-samll-attempt0.in", "r");

     out = fopen("A-large-result.out", "wt");

    

     int testcase; //测试数据组数

     scanf("%d", &testcase);

     int exp;

    

     for(int caseId = 1; caseId <= testcase; caseId++)

     {

         fprintf(out, "Case #%d: ", caseId);

         scanf("%d%d", &n, &exp);   

        

         for(int i = 1; i <= (n-1)/2; i++)

         {

              scanf("%d%d", &G[i], &C[i]);

         }

         for(int i = (n-1)/2+1; i <= n; i++)

         {

              scanf("%d", &X[i]);

         }

        

         memset(f, -1, sizeof(f));

        

         //运算

         int ret = Solve(1,exp);

         //输出

         if(ret == impossible)

              fprintf(out, "IMPOSSIBLE/n");

         else

              fprintf(out, "%d/n", ret);

        

     }

    

     return 0;

}

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值