POJ 3134 Power Calculus(AOJ 1271)

http://poj.org/problem?id=3134 https://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=1271 https://www.spoj.com/problems/YOKOF/en/

https://vjudge.net/problem/POJ-3134 https://vjudge.net/problem/Aizu-1271 https://vjudge.net/problem/SPOJ-YOKOF ※POJのジャッジだと通っていない

問題概要

  • 正整数の乗算・除算ができるときに、正整数$x$の$n$乗を高速に求めたい
    • (繰り返し二乗法のようにすれば$\log_2$回程度でできるが、これは最小ではない
  • $n$が与えられるので、$x^n$を計算する際の最小の乗算・除算の回数を求めよ

制約

$1\leq n\leq 1000$

解法メモ

Project Eulerに似た問題があった気がする

  • 枝刈り全探索を実装するが、小手先の枝刈りをごにょごにょしていたら通ってしまった
  • 最適化込みだと1秒くらいで実行できるが、POJは最適化オプションを付けてくれないので通っていない

実装例

現在までに見た最大値を使って$N$が作れそうなら作り、無理なら再帰するといった適当な枝刈り。おそらく参考にはならない

#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <ctime>
#include <iostream>
#include <map>
#include <vector>
typedef long long ll;
#define rep(i, n) for (int i = 0, i##_len = (n); i < i##_len; ++i)
using namespace std;
#define all(v) v.begin(), v.end()
#define rall(v) v.rbegin(), v.rend()

vector<int> hashes;

vector<int> used;
map<int, int> memo;
vector<int> uppers;
vector<bool> seen;
int n;

int g(int x, int d = 0) {  // 普通の繰り返し二乗法を上界として使う
    if (x <= 1) return d;
    if (x % 2 == 0) return g(x / 2, d + 1);
    return g(x - 1, d + 1);
}

int rec(int upper) {
    if (used.size() >= upper) return upper;
    int maxUsed = *max_element(all(used));

    // 枝刈り。最大値を2倍していってそれだけでnを作れるなら作る
    int x = maxUsed, c = 0;
    int osz = used.size();
    while (x < n) {
        x *= 2;
        used.push_back(x);
        c++;
    }
    int usz = used.size();
    if (x == n) {
        used.resize(osz);
        return usz - 1;
    } else {
        if (c != 0) {
            rep(i, usz - 1) {
                if (x / 2 + used.at(i) == n) {
                    used.resize(osz);
                    return usz - 1;
                }
            }
        }
        rep(i, usz) {
            if (x - used.at(i) == n) {
                used.resize(osz);
                return usz;
            }
        }
        if (usz - 1 >= upper) {
            used.resize(osz);
            return upper;
        }
        used.resize(osz);
    }

    // 再帰先の候補を列挙
    int res = upper;
    vector<int> candidates;

    for (size_t i = 0; i < used.size(); i++) {
        int x = used[i];
        int sum = x + maxUsed;
        int dif = maxUsed - x;
        if (sum == n || dif == n) {
            return used.size();
        }
        if (sum != 0 && sum < 2 * n && !seen[sum]) {
            uppers[sum] = min<int>(uppers[sum], used.size());
            candidates.push_back(sum);
        }
        if (dif != 0 && !seen[dif]) {
            uppers[dif] = min<int>(uppers[dif], used.size());
            candidates.push_back(dif);
        }
    }
    sort(rall(candidates));
    candidates.erase(unique(all(candidates)), candidates.end());

    // バックトラック
    for (size_t i = 0; i < candidates.size(); i++) {
        int next = candidates[i];
        used.push_back(next);
        seen[next] = true;
        int r = rec(res);
        if (res > r) res = r;
        seen[next] = false;
        used.pop_back();
    }
    return res;
}

int main() {
    uppers.resize(2049);
    rep(i, 2049) uppers[i] = g(i);
    while (true) {
        cin >> n;
        if (n == 0) break;
        if (n == 1) {
            cout << 0 << endl;
            continue;
        }
        seen = vector<bool>(n * 2 + 1);
        seen[1] = true;
        seen[2] = true;
        used.clear();
        memo.clear();
        used.push_back(1);
        used.push_back(2);
        cout << rec(uppers[n]) << endl;
    }
}