天天看點

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個子結點。

布爾樹特殊之處在于每個節點都擁有一個值,1或0。此外,每個内部結點(非葉子結點)都有一個“與門”或“或門”。内部結點的取值是通過其兩個子結點進行對應的“與”或“或”運算的值。所有葉結點的值是通過輸入獲得,是以所有結點的值都可以通過計算獲得。

樹的根結點是我們特别關注的。我們希望根結點的取值是指定的值V,1或者0。不幸的是,往往根結點實際的值并不是我們所希望的。然而幸運的是,我們可以通過改變某些結點的門來得到希望的取值。

給定一棵布爾樹和樹中可以改變門的結點,找出需要改變最少多少個門才可以得到想要的取值,如果不能得到值,則輸出”IMPOSSIBLE”。

輸入

輸入的第一行包含測試資料的組數,N。後面跟着N組測試資料。

每組資料第一行有兩個數M和V。M訓示這棵樹有多少結點,它能夠保證每個結點都有0個或2個子結點。V表示希望的根結點取值,0或1。

接下來M行描述樹中的節點,第X行描述第X個結點。

前(M-1)/2行描述内部結點。每行有兩個數G和C,每個取值都是0或1。G表示結點的門,1代表與門,0表示或門。C表示這個結點的門是否可以改變,0表示不能改變,1表示可以變動。

接下來(M+1)/2行描述葉子結點。每行包含一個數I,0或1,葉結點的值。

下圖中是個執行個體:

輸出

每組測試資料輸出下面的格式:

Case #X: Y

X指第幾組,Y表示最少要變動幾個門,如果不能得到需要的值,則Y為IMPOSSIBLE。

限制

1<N<=20

小測試資料:

最多30個結點

大測試資料:

最多10000個結點。

示例

Input

2

9 1

1 0

1 1

1 1

0 0

1

1

1

5 0

1 1

0 0

1

1

Output

Case #1: 1

Case #2: IMPOSSIBLE

解題思路

總的說來,這道題的本質是考遞歸的應用,同時需要考慮對于中間狀态的儲存。

#include <cstdio>

#include <iostream>

using namespace std;

//比較a和b,如果b比a小,則将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;

}