一叶知秋。如果要平衡,那么,末端的二叉左右两片叶子一定是相等的。好吧,延伸开来,所有的左右子树都要是相等的。所以,由一片叶子就可以推知整棵树的情形。
如果使用3作为参考砝码,那么整个天平的重量是 3×2^2=12,需要修改砝码7为3。
如果使用7作为参考砝码,那么整个天平的重量是7×2^2=28,需要修改砝码3为7,6为14。
如果使用6作为参考砝码,那么整个天平的重量是6×2^1=12,需要修改砝码7为3。
我们发现三次参考砝码的选择中,有两次得到的天平总重量都是12,也就是说,这两个砝码如果修改其中一个,另一个就不用改。那么我们把使用每一个砝码得到的天平总重记录起来,找到其中出现次数最多的那个,然后我们用砝码数减去它就是最少修改砝码数了。
那么,这里背后的数学原理是什么呢?
稍微想一下其实是比较容易证明的,或者说容易厘清的。为什么呢?因为只要最后得到的天平的重量是一样的,那么所有会导致这种重量的方案的砝码其实是都不用动的。减掉这些不用动的,自然就是需要修改的砝码的数量。
#include <cassert>
#include <cmath>
#include <string>
#include <iostream>
#include <functional>
#include <algorithm>
#include <map>
using namespace std;
const int MAXN = 24;
typedef long long LL;
int main()
{
int T;
cin >> T; // 读入测试用例数量 T
string line;
map<LL, int> vCnt; // 使用map来记录每个节点值的出现次数
while (T--)
{
cin >> line; // 读入一行表示平衡二叉树的表达式
vCnt.clear(); // 清空上一个测试用例的节点值统计
int sz = line.size(), depth = 0, nodeCnt = 0; // sz表示字符串长度,depth表示当前节点深度,nodeCnt表示节点总数
for (int i = 0; i < sz; i++)
{
char c = line[i];
if (c == '[')
depth++; // 遇到'[',深度加一
else if (c == ']')
depth--; // 遇到']',深度减一
else if (isdigit(c))
{
// 遇到数字,解析节点值
LL v = c - '0';
int j;
for (j = i + 1; j < sz && isdigit(line[j]); j++)
{
v *= 10;
v += line[j] - '0';
}
i = j - 1;
v <<= depth; // 节点值左移depth位,考虑到当前节点的深度,也就是上面所说的,使用 v 这个值作为参考砝码的情况
vCnt[v] = vCnt[v] + 1; // 更新节点值的出现次数
nodeCnt++; // 节点总数加一
}
}
int K = -1;
for (const auto &p : vCnt)
K = max(K, p.second); // 找到节点值出现次数的最大值
assert(K > 0); // 断言确保K大于0
cout << (nodeCnt - K) << endl; // 输出结果,节点总数减去出现次数最多的节点值的次数
}
return 0;
}