作为一个算法的不入门兴趣者,玩着参加了下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
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;
//比较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;
}