suganoさんの作品(ソースコード)

//------------------------------------------------------------------------------
/// @file
/// @author   ハル研究所プログラミングコンテスト実行委員会
///
/// @copyright  Copyright (c) 2018 HAL Laboratory, Inc.
/// @attention  このファイルの利用は、同梱のREADMEにある
///             利用条件に従ってください。
//------------------------------------------------------------------------------
#include "Answer.hpp"

#include <random>

int stage = 0;

//------------------------------------------------------------------------------
/// ハッシュ値の計算に使用するランダムな64ビットの値。2^Bits(=1024)個。
/// 適度にランダムであれば何でもいいけど、mt19937_64 で生成してみる。
class Hash {
public:
    static const int Bits = 10;
    static uint64_t Table[1 << Bits];
public:
    Hash() {
        std::mt19937_64 rand;
        for (int i = 0; i < 1 << Bits; i++) {
            Table[i] = rand();
        }
    }
};
uint64_t Hash::Table[1 << Hash::Bits];
Hash RandomTableInitializer;

//------------------------------------------------------------------------------
/// 3次元ベクトル。
struct Vector3i {
public:
    int X;
    int Y;
    int Z;
    Vector3i() {
        X = 0;
        Y = 0;
        Z = 0;
    }
    Vector3i(int x, int y, int z)
    {
        X = x;
        Y = y;
        Z = z;
    }
    int Weight() {
        return X * Y * (Z + 10);
    }
    bool operator==(Vector3i rhs) {
        if (X == rhs.X && Y == rhs.Y && Z == rhs.Z) {
            return true;
        }
        else {
            return false;
        }
    }
};

//------------------------------------------------------------------------------
/// 生地の位置とサイズ。
/// 生地は位置、サイズともに3次元ベクトルで表現する。
/// 位置のzはオーブンに入れる時間。
/// サイズのzは焼けるまでの所要時間。
/// これで、20*20*1000の箱詰め問題となる。
/// (同時に2つ焼けないので、2つ以上の位置のzが同じにならないように注意する必要がある)
struct Box {
public:
    Vector3i Position;
    Vector3i Size;
    Box() {
    }
    Box(Vector3i position, Vector3i size) {
        Position = position;
        Size = size;
    }
public:
    /// 2つの生地が衝突しているかを判定する。
    bool Collided(Box& target) {
        if (Position.X + Size.X <= target.Position.X) {
            return false;
        }
        if (target.Position.X + target.Size.X <= Position.X) {
            return false;
        }
        if (Position.Y + Size.Y <= target.Position.Y) {
            return false;
        }
        if (target.Position.Y + target.Size.Y <= Position.Y) {
            return false;
        }
        if (Position.Z + Size.Z <= target.Position.Z) {
            return false;
        }
        if (target.Position.Z + target.Size.Z <= Position.Z) {
            return false;
        }
        return true;
    }
    bool operator==(Box& rhs) {
        if (Position == rhs.Position && Size == rhs.Size) {
            return true;
        }
        else {
            return false;
        }
    }
    /// この生地の位置とサイズのハッシュ値。
    uint64_t GetHashCode() {
        uint64_t hash = Position.X + (Position.Y << 5);
        hash = Hash::Table[hash & ((1 << Hash::Bits) - 1)] ^ (hash >> Hash::Bits);
        hash += Position.Z + 256;
        hash = Hash::Table[hash & ((1 << Hash::Bits) - 1)] ^ (hash >> Hash::Bits);
        hash += Size.X + (Size.Y << 5);
        hash = Hash::Table[hash & ((1 << Hash::Bits) - 1)] ^ (hash >> Hash::Bits);
        hash += Size.Z;
        hash = Hash::Table[hash & ((1 << Hash::Bits) - 1)] ^ (hash >> Hash::Bits);
        hash = Hash::Table[hash & ((1 << Hash::Bits) - 1)] ^ (hash >> Hash::Bits);
        return hash;
    }
};

//------------------------------------------------------------------------------
/// 時間経過も込みで管理するオーブン。
class Oven3D {
public:
    static const int PieceCapacity = 100;
    static const int ZMax = 2000;
    hpc::Array<Box, PieceCapacity> Pieces;
    /// 格納済みのBoxを配置の時間が早いものから順に並べたときのインデックス。
    int ZOrders[PieceCapacity];
    /// その時間に置く生地があるかどうか。(同時に2つ焼けないため)
    bool Busy[ZMax];
    uint64_t HashCode;
    Oven3D()
    {
        for (int i = 0; i < ZMax; i++) {
            Busy[i] = false;
        }
        HashCode = 0;
    }
public:
    /// 1ターン進める。
    /// Position.Z から1引くだけ。
    /// Position.Z が負のときは、前のターンまでに置いた生地。
    /// Position.Z + Size.Z が 0 になると、焼き上がり。
    void Update() {
        for (int i = 0; i < Pieces.count(); i++) {
            auto& piece = Pieces[ZOrders[i]];
            HashCode ^= piece.GetHashCode();
            if (piece.Position.Z + piece.Size.Z <= 0) {
                RemoveAt(ZOrders[i]);
                i--;
            }
            else {
                int z = piece.Position.Z;
                if (z >= 0) {
#if _DEBUG
                    HPC_ASSERT(Busy[z] == true);
#endif
                    Busy[z] = false;
                }
                piece.Position.Z--;
                z--;
                if (z >= 0) {
#if _DEBUG
                    HPC_ASSERT(Busy[z] == false);
#endif
                    Busy[z] = true;
                }
                HashCode ^= piece.GetHashCode();
            }
        }
    }
    /// 配置済みの生地との衝突判定。
    bool Collided(Box& c) {
        for (int i = 0; i < Pieces.count(); i++) {
            if (Pieces[i].Collided(c)) {
                return true;
            }
        }
        return false;
    }
    /// オーブンに生地を追加する。
    void Add(Box& c) {
#if _DEBUG
        HPC_ASSERT(Busy[c.Position.Z] == false);
#endif
        Busy[c.Position.Z] = true;
        Pieces.add(c);
        int count = Pieces.count();
        ZOrders[count - 1] = count - 1;
        for (int i = count - 1; i > 0; i--) {
            if (Pieces[ZOrders[i]].Position.Z < Pieces[ZOrders[i - 1]].Position.Z) {
                int t = ZOrders[i];
                ZOrders[i] = ZOrders[i - 1];
                ZOrders[i - 1] = t;
            }
            else {
                break;
            }
        }
        HashCode ^= c.GetHashCode();
    }
    /// オーブンから生地を取り除く。
    void RemoveAt(int index) {
#if _DEBUG
        HPC_ASSERT(index >= 0 && index < Count());
#endif
        int z = Pieces[index].Position.Z;
        if (z >= 0) {
#if _DEBUG
            HPC_ASSERT(Busy[z] == true);
#endif
            Busy[z] = false;
        }
        for (int i = 0; i < Pieces.count(); i++) {
            if (ZOrders[i] == index) {
                for (int j = i; j < Pieces.count() - 1; j++) {
                    ZOrders[j] = ZOrders[j + 1];
                }
                break;
            }
        }
        for (int i = 0; i < Pieces.count(); i++) {
            if (ZOrders[i] > index) {
                ZOrders[i]--;
            }
        }
        HashCode ^= Pieces[index].GetHashCode();
        Pieces.erase(index);
    }
    int Count() {
        return Pieces.count();
    }
    /// 指定したサイズの生地を置くことができる、最も小さいZを座標ごとに求める。
    /// オーブンを3次元の箱と見立てて、配置しようとしている生地が入る場所を求める。
    /// 戻り値は共有バッファ SharedZS に書き込まれる。
    int SharedZS[hpc::Parameter::OvenHeight][hpc::Parameter::OvenWidth];
    void CalcVacantZs(Vector3i size, bool zFirst) {
        int sh = hpc::Parameter::OvenHeight + 1 - size.Y;
        int sw = hpc::Parameter::OvenWidth + 1 - size.X;
        int minZ = 0;
        for (int i = 0; ; i++) {
            if (Busy[i]) {
                minZ++;
            }
            else {
                break;
            }
        }
        for (int i = 0; i < sh; i++) {
            for (int j = 0; j < sw; j++) {
                SharedZS[i][j] = minZ;
            }
        }

        bool minZFound = false;
        for (int pi = 0; pi < Pieces.count() && minZFound == false; pi++) {
            auto& piece = Pieces[ZOrders[pi]];
            int ymin = piece.Position.Y - size.Y + 1;
            int ymax = piece.Position.Y + piece.Size.Y;
            if (ymin < 0) {
                ymin = 0;
            }
            if (ymax > sh) {
                ymax = sh;
            }
            int xmin = piece.Position.X - size.X + 1;
            int xmax = piece.Position.X + piece.Size.X;
            if (xmin < 0) {
                xmin = 0;
            }
            if (xmax > sw) {
                xmax = sw;
            }
            for (int i = ymin; i < ymax; i++) {
                for (int j = xmin; j < xmax; j++) {
                    if (SharedZS[i][j] + size.Z <= piece.Position.Z ||
                        piece.Position.Z + piece.Size.Z <= SharedZS[i][j]) {
                        if (zFirst) {
                            if (SharedZS[i][j] + size.Z <= piece.Position.Z) {
                                minZFound = true;
                            }
                        }
                    }
                    else {
                        SharedZS[i][j] = piece.Position.Z + piece.Size.Z;
                        while (Busy[SharedZS[i][j]]) {
                            SharedZS[i][j]++;
                        }
                    }
                }
            }
        }
    }
    /// オーブンに配置されている生地全体のハッシュ値。
    /// 各生地のハッシュ値をxorしたもの。
    uint64_t GetHashCode() {
        return HashCode;
    }
    void Dump() {
        std::printf("----------------------------------------\n");
        for (int i = 0; i < Pieces.count(); i++) {
            std::printf("(%d, %d, %d), (%d, %d, %d)\n", Pieces[i].Position.X, Pieces[i].Position.Y, Pieces[i].Position.Z, Pieces[i].Size.X, Pieces[i].Size.Y, Pieces[i].Size.Z);
        }
        std::printf("----------------------------------------\n");
    }
};

//------------------------------------------------------------------------------
/// Actionを13ビットの値で表現したもの。
struct CompactAction {
public:
    static const int Wait = 8191;
    static int FromXYPieceID(int x, int y, int pieceID) {
#if _DEBUG
        HPC_ASSERT(x >= 0 && x < hpc::Parameter::OvenWidth);
        HPC_ASSERT(y >= 0 && y < hpc::Parameter::OvenHeight);
        HPC_ASSERT(pieceID >= 0 && pieceID < 2 * hpc::Parameter::CandidatePieceCount);
#endif
        return ((y * hpc::Parameter::OvenWidth + x) << 4) | pieceID;
    }
    static int X(int image) {
        return (image >> 4) % hpc::Parameter::OvenWidth;
    }
    static int Y(int image) {
        return (image >> 4) / hpc::Parameter::OvenWidth;
    }
    static int PieceID(int image) {
        return image & 15;
    }
};

//------------------------------------------------------------------------------
/// 探索結果の値と、そのときの行動。
/// スコアは、小さい方がいいので、ペナルティという呼び方をする。
/// なお、ペナルティは、レーンで待機中の全ての生地を配置したときの、生地のペナルティの合計。
/// 生地のペナルティは、体積と、配置する時間(1ターン増えるごとに1増える)をかけた値。
struct SearchResult {
public:
    int Penalty;
    int CompactAction;
    SearchResult(int penalty, int compactAction)
    {
        Penalty = penalty;
        CompactAction = compactAction;
    }
};

//------------------------------------------------------------------------------
/// 探索結果のキャッシュ。
/// Searchで探索した結果を保存しておく。
/// いわゆるDPのテーブルのようなものだが、全部のオーブンの状態を保存するメモリはないので、
/// オーブンのハッシュ値でキャッシュする。
/// オーブンのハッシュ値のうち、下位14ビットとその上の32ビットの合計46ビット、
/// および使用済み生地が一致するものは、同じノードとみなす。
/// (もしかしたら違うかもしれないけど、非常に確率が低いのと、探索を少し間違える
/// だけで、大きな問題はないと思うので気にしない)
/// ハッシュ値のうち、下位14ビットは、テーブルのどこに入るかで決まるので保存しない。
struct CacheNode {
public:
    uint32_t MiniHash;
    uint16_t Used;
    uint16_t CompactAction;
    int Value;
    CacheNode() {
        MiniHash = 0;
        Used = 0;
        CompactAction = 0;
        Value = 0;
    }
    CacheNode(uint32_t miniHash, uint16_t used, uint16_t compactAction, int value) {
        MiniHash = miniHash;
        Used = used;
        CompactAction = compactAction;
        Value = value;
    }
};
//------------------------------------------------------------------------------
/// 探索結果のキャッシュ。
/// ハッシュテーブルのサイズは2^14個(16384個)。
class Cache {
public:
    static const int TagBitLength = 14;
    static const int BlockCapacity = 200;
    static hpc::Array<CacheNode, BlockCapacity> SearchResultCache[1 << TagBitLength];
    static void AddResultCache(SearchResult result, uint64_t hash, int used) {
        int tag = hash & ((1 << TagBitLength) - 1);
        uint32_t miniHash = (uint32_t)(hash >> TagBitLength);
        auto& cacheBlock = SearchResultCache[tag];
        uint16_t used16 = (uint16_t)used;
        for (int i = 0; i < cacheBlock.count(); i++) {
            if (cacheBlock[i].MiniHash == miniHash && cacheBlock[i].Used == used16) {
                if (cacheBlock[i].Value != result.Penalty ||
                    cacheBlock[i].CompactAction != result.CompactAction) {
#if _DEBUG
                    std::printf("Cache hash conflicts. %d, %08x\n", tag, miniHash);
                    HPC_ASSERT(false);
#endif
                }
                return;
            }
        }
        if (cacheBlock.count() < BlockCapacity - 1) {
            cacheBlock.add(CacheNode(miniHash, used16, (uint16_t)result.CompactAction, result.Penalty));
        }
    }
    static CacheNode* FindResultCache(uint64_t hash, int used) {
        int tag = hash & ((1 << TagBitLength) - 1);
        uint32_t miniHash = (uint32_t)(hash >> TagBitLength);
        auto& cacheBlock = SearchResultCache[tag];
        uint16_t used16 = (uint16_t)used;
        for (int i = 0; i < cacheBlock.count(); i++) {
            if (cacheBlock[i].MiniHash == miniHash && cacheBlock[i].Used == used16) {
                return &(cacheBlock[i]);
            }
        }
        return nullptr;
    }
};
hpc::Array<CacheNode, Cache::BlockCapacity> Cache::SearchResultCache[1 << Cache::TagBitLength];

//------------------------------------------------------------------------------
/// ソルバー。
class Solver {
public:
    int Frame;
    Oven3D CurrentOven;
    Solver()
    {
        Frame = 0;
        Box ceil(Vector3i(0, 0, hpc::Parameter::GameTurnLimit + 1), Vector3i(hpc::Parameter::OvenWidth, hpc::Parameter::OvenHeight, 1));
        CurrentOven.Add(ceil);
    }
public:
    /// 指定した生地を置くべき位置。
    /// なるべく早くおけるところを優先し、置くタイミングが同じなら、外周に近いところを優先する。
    Box PutPos(Vector3i& piece) {
        const int PenaltyMax = 100000000;
        CurrentOven.CalcVacantZs(piece, true);
        Box maxCol;
        int minPenalty = PenaltyMax;
        for (int i = 0; i < hpc::Parameter::OvenHeight + 1 - piece.Y; i++) {
            for (int j = 0; j < hpc::Parameter::OvenWidth + 1 - piece.X; j++) {
                int yDist = std::min(i, hpc::Parameter::OvenHeight - piece.Y - i);
                int xDist = std::min(j, hpc::Parameter::OvenWidth - piece.X - j);
                int penalty = xDist + yDist + std::min(xDist, yDist) * 100 + CurrentOven.SharedZS[i][j] * 10000;
                if (penalty < minPenalty) {
                    minPenalty = penalty;
                    maxCol = Box(Vector3i(j, i, CurrentOven.SharedZS[i][j]), piece);
                }
            }
        }
        return maxCol;
    }
    /// オーブンのペナルティ。なるべく広く開いているものがペナルティが小さくなる。
    /// その方が、将来、置く場所の選択肢が増えるため。
    /// (ただし、これを入れてもあまり変わらないのと、処理時間がかかるので、今は使ってない)
    int OvenPenalty() {
        int sizex = 5;
        int sizey = 5;
        int sizez = 100;
        int penalty = 0;
        CurrentOven.CalcVacantZs(Vector3i(sizex, sizey, sizez), false);
        for (int i = 0; i < hpc::Parameter::OvenHeight + 1 - sizey; i++) {
            for (int j = 0; j < hpc::Parameter::OvenWidth + 1 - sizex; j++) {
                penalty += CurrentOven.SharedZS[i][j];
            }
        }
        return penalty;
    }
    /// 生地のペナルティ。
    /// 生地の体積と、配置する時間(1ターン遅くなるごとに1増える)をかけた値。
    /// つまり、大きいものを後に置くほどペナルティが大きくなる。
    int PiecePenalty(Box piece) {
        int s = piece.Position.Z * piece.Size.Z;
        s *= piece.Size.X * piece.Size.Y;
        return s;
    }
    /// 現在のオーブンに、残りの生地を全部置いたときのペナルティを求める。
    /// 生地を置く場所は、その生地にとって最善の位置(なるべく早く置けて、なるべく端に近いところ)とする。
    /// 置き方を決める順番をいろいろ変えてみて、最もペナルティの小さいものを採用する。
    /// (置く順番ではなく、置く場所と時間を決める順番なので注意!)
    /// usedの各ビットで、その生地をすでに置いているかどうかを示す。
    /// depthは、探索の深さ。depthが2以上なら次に置く生地を残りの生地すべてで試す。
    /// depthが1なら、残りの中から10個ぐらいで試す。
    /// depthが0なら、残りの生地を大きいものから順に置いて試す。
    /// countは残り個数。
    /// limitは、ペナルティがこの値と等しい、または大きくなることが確定したときに、探索を中止する値。
    /// そのときは、戻り値として、limitに等しい値、行動はWaitを返す。
    SearchResult Search(hpc::Array<Vector3i, 2 * hpc::Parameter::CandidatePieceCount>& pieces,
        int used,
        int depth,
        int count,
        int limit
        ) {
        // limitが0または負のときは、探索しなくてもいい。
        if (limit <= 0) {
            return SearchResult(limit, CompactAction::Wait);
        }
        // 計算済みのものがキャッシュされていたら、それを返す。
        uint64_t hash = CurrentOven.GetHashCode();
        CacheNode* cacheNode = Cache::FindResultCache(hash, used);
        if (cacheNode != nullptr) {
            return SearchResult(cacheNode->Value, cacheNode->CompactAction);
        }

        SearchResult minResult(limit, CompactAction::Wait);
        for (int pi = 0; pi < pieces.count(); pi++) {
            if (((used & (1 << pi))) != 0) {
                continue;
            }
            auto& piece = pieces[pi];
            Box p = PutPos(piece);
            CurrentOven.Add(p);
            int penalty;
            if (count == 1) {
                penalty = 0; // OvenPenalty();
                penalty += PiecePenalty(p);
                CurrentOven.RemoveAt(CurrentOven.Count() - 1);
                if (penalty > limit) {
                    penalty = limit;
                    return SearchResult(penalty, CompactAction::Wait);
                }
                auto result = SearchResult(penalty, p.Position.Z == 0 ? CompactAction::FromXYPieceID(p.Position.X, p.Position.Y, pi) : CompactAction::Wait);
                Cache::AddResultCache(result, hash, used);
                return result;
            }
            else {
                int lpenalty = PiecePenalty(p);
                if (limit - lpenalty <= 0) {
                    CurrentOven.RemoveAt(CurrentOven.Count() - 1);
                    return SearchResult(limit, CompactAction::Wait);
                }
                SearchResult result = Search(pieces, (used | (1 << pi)), depth - 1, count - 1, minResult.Penalty - lpenalty);
                result.Penalty += lpenalty;
                CurrentOven.RemoveAt(CurrentOven.Count() - 1);
                if (result.Penalty < minResult.Penalty) {
                    minResult = result;
                    if (p.Position.Z == 0) {
                        minResult.CompactAction = CompactAction::FromXYPieceID(p.Position.X, p.Position.Y, pi);
                    }
                }
                if (depth <= 0) {
                    Cache::AddResultCache(minResult, hash, used);
                    return minResult;
                }
                if (depth == 1 && pi >= 10) {
                    Cache::AddResultCache(minResult, hash, used);
                    return minResult;
                }
            }
        }
        Cache::AddResultCache(minResult, hash, used);
        return minResult;
    }
    int NextAction(hpc::Array<Vector3i, 2 * hpc::Parameter::CandidatePieceCount>& pieces) {
        Frame++;
        for (int i = 0; i < 1 << Cache::TagBitLength; i++) {
            Cache::SearchResultCache[i].clear();
        }
        CurrentOven.Update();

        int pieceOrders[2 * hpc::Parameter::CandidatePieceCount];
        for (int i = 0; i < 2 * hpc::Parameter::CandidatePieceCount; i++) {
            pieceOrders[i] = i;
        }
        // 大きい生地ほど、早く配置を決められるように、並べ替え。
        for (int i = 2 * hpc::Parameter::CandidatePieceCount - 1; i > 0; i--) {
            for (int j = 0; j < i; j++) {
                if (pieces[j].Weight() < pieces[j + 1].Weight()) {
                    auto t = pieces[j];
                    pieces[j] = pieces[j + 1];
                    pieces[j + 1] = t;
                    int to = pieceOrders[j];
                    pieceOrders[j] = pieceOrders[j + 1];
                    pieceOrders[j + 1] = to;
                }
            }
        }

        SearchResult result = Search(pieces, 0, 2, 2 * hpc::Parameter::CandidatePieceCount, 1000000000);
        int a = result.CompactAction;
        if (a != CompactAction::Wait) {
            auto box = Box(Vector3i(CompactAction::X(a), CompactAction::Y(a), 0), pieces[CompactAction::PieceID(a)]);
            CurrentOven.Add(box);
            // 並びを元に戻す
            int originalAction = CompactAction::FromXYPieceID(CompactAction::X(a), CompactAction::Y(a), pieceOrders[CompactAction::PieceID(a)]);
            return originalAction;
        }
        else {
            return a;
        }
    }
};

Solver* solver;

//------------------------------------------------------------------------------
namespace hpc {

//------------------------------------------------------------------------------
/// コンストラクタ。
/// @detail 最初のステージ開始前に実行したい処理があればここに書きます。
Answer::Answer()
{
}

//------------------------------------------------------------------------------
/// デストラクタ。
/// @detail 最後のステージ終了後に実行したい処理があればここに書きます。
Answer::~Answer()
{
}

//------------------------------------------------------------------------------
/// 各ステージ開始時に呼び出される処理。
/// @detail 各ステージに対する初期化処理が必要ならここに書きます。
/// @param aStage 現在のステージ。
void Answer::init(const Stage& aStage)
{
    solver = new Solver();
}

//------------------------------------------------------------------------------
/// このターンでの行動を指定する。
/// @detail 希望する行動を Action の static 関数で生成して return してください。
///         正常ではない行動を return した場合、何も起きません。
///         詳細は Action のヘッダを参照してください。
/// @param aStage 現在のステージ。
Action Answer::decideNextAction(const Stage& aStage)
{
    Array<Vector3i, 2 * Parameter::CandidatePieceCount> pieces;
    for (int i = 0; i < 2; i++) {
        auto laneType = i == 0 ? hpc::CandidateLaneType_Large : hpc::CandidateLaneType_Small;
        for (int j = 0; j < Parameter::CandidatePieceCount; j++) {
            auto& piece = aStage.candidateLane(laneType).pieces()[j];
            pieces.add(Vector3i(piece.width(), piece.height(), piece.requiredHeatTurnCount() + 1));
        }
    }

    int next = solver->NextAction(pieces);
    if (next == CompactAction::Wait) {
        return Action::Wait();
    }
    else {
        int pieceID = CompactAction::PieceID(next);
        auto laneType = pieceID / hpc::Parameter::CandidatePieceCount == 0 ? CandidateLaneType_Large : hpc::CandidateLaneType_Small;
        int piece = pieceID % hpc::Parameter::CandidatePieceCount;
        return Action::Put(laneType, piece, Vector2i(CompactAction::X(next), CompactAction::Y(next)));
    }
}

//------------------------------------------------------------------------------
/// 各ステージ終了時に呼び出される処理。
/// @detail 各ステージに対する終了処理が必要ならここに書きます。
/// @param aStage 現在のステージ。
void Answer::finalize(const Stage& aStage)
{
    delete solver;
    stage++;
}

} // namespace
// EOF