UVA-12558 埃及分数 题解答案代码 算法竞赛入门经典第二版

GitHub - jzplp/aoapc-UVA-Answer: 算法竞赛入门经典 例题和习题答案 刘汝佳 第二版

这道题我卡了很久很久,已经通过了样例和udebug上的所有例子(除了那个错的例子),但一直WA。最后我发现是一个udebug上没有覆盖的场景导致的。就是我们已经计算出1/x结果,但这个x可能因为重复或者命中不能作为答案,此时应该继续把1/x拆解,而不是结束退出。

具体方法还是采用迭代加深搜索,每次增加一个分数的数量,直到找到答案。分数从大到小搜寻(因为分子是1,对应的分母实际上是越来越大)。而且有几个剪枝情况,例如最大的分数不能大于当前剩余值,最小的分数*剩余次数必须要大于当前剩余值等等。

AC代码

#include <stdio.h>
#include <string.h>
#include <cstdint>
#include <set>

using namespace std;
#define MAXRESTI 10000

// 不能选的数字
uint64_t restrictArr[MAXRESTI + 50];
// 最小的组合
set<uint64_t> seMin;
// 当前组合
set<uint64_t> seTemp;

// 求最大公约数 辗转相除法
uint64_t gcd(uint64_t x, uint64_t y)
{
  uint64_t t;
  while (x % y)
  {
    t = x % y;
    x = y;
    y = t;
  }
  return y;
}

// 计算 a/b - 1/egyp 的结果分数
void getSubFrac(uint64_t a, uint64_t b, uint64_t egyp, uint64_t &at, uint64_t &bt)
{
  uint64_t gcdV = gcd(b, egyp);
  uint64_t tb, te;
  tb = b / gcdV;
  te = egyp / gcdV;
  at = a * te - tb;
  bt = b * te;
  if (at <= 0) 
    return;
  uint64_t gcdVt = gcd(bt, at);
  at = at / gcdVt;
  bt = bt / gcdVt;
}

// seTemp与当前seMin比较
bool compareSet()
{
  if (seMin.empty())
    return true;
  if (seMin.size() > seTemp.size())
    return true;
  if (seMin.size() < seTemp.size())
    return false;
  for (auto rm = seMin.rbegin(), rt = seTemp.rbegin(); rm != seMin.rend(); ++rm, ++rt)
  {
    if (*rm > *rt)
      return true;
    if (*rm < *rt)
      return false;
  }
  return false;
}

// 判断我们能否使用这个数字
bool judgeNum(uint64_t i)
{
  if (i > MAXRESTI)
  {
    if (seTemp.count(i))
      return false;
    return true;
  }
  if (restrictArr[i] || seTemp.count(i))
    return false;
  return true;
}

// 分数 a/b, time还剩的循环次数, prev上次循环到的数字+1
void computed(uint64_t a, uint64_t b, uint64_t time, uint64_t prev)
{
  if (time < 0)
    return;
    if (time == 0)
  {
    if (a == 1 && judgeNum(b))
    {
      seTemp.insert(b);
      if (compareSet())
        seMin = seTemp;
      seTemp.erase(b);
    }
    return;
  }
  // 获取比这个分数小的埃及分数中最大的那个 必须+1不然会出现负数
  uint64_t minNum = b / a + 1;
  uint64_t i = minNum > prev ? minNum : prev;
  uint64_t at, bt;
  for (;; ++i)
  {
    // 比当前答案的最高数字更大
    if (!seMin.empty() && (i > *(seMin.rbegin())))
      return;
    // 循环最大值
    if (a * i > b * (time + 1))
      return;
    if (!judgeNum(i))
      continue;
    getSubFrac(a, b, i, at, bt);
    // 找到了
    seTemp.insert(i);
      computed(at, bt, time - 1, i + 1);
    seTemp.erase(i);
  }
}

int main()
{
  uint64_t a, b, step, k, t, ti = 0, i;
  scanf("%llu", &t);
  while (++ti <= t)
  {
    scanf("%llu %llu %llu", &a, &b, &k);
    memset(restrictArr, 0, sizeof(restrictArr));
    seMin.clear();
    seMin = set<uint64_t>();
    seTemp.clear();
    seTemp = set<uint64_t>();
    while (k--)
    {
      scanf("%llu", &i);
      restrictArr[i] = 1;
    }
    for (step = 1;; ++step)
    {
      seMin.clear();
      seMin = set<uint64_t>();
      seTemp.clear();
      seTemp = set<uint64_t>();
      computed(a, b, step, 2);
      if (!seMin.empty())
        break;
    }
    printf("Case %llu: %llu/%llu=", ti, a, b);
    for (auto it = seMin.begin(); it != seMin.end(); ++it)
    {
      if (it != seMin.begin())
        putchar('+');
      printf("1/%llu", *it);
    }
    putchar('\n');
  }
  return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值