//------------------------------------------------------------------------------
/// @file
/// @author ハル研究所プログラミングコンテスト実行委員会
///
/// @copyright Copyright (c) 2018 HAL Laboratory, Inc.
/// @attention このファイルの利用は、同梱のREADMEにある
/// 利用条件に従ってください。
//------------------------------------------------------------------------------
#include "Answer.hpp"
#include <iostream>
#include <vector>
#include <algorithm>
#include <cstdlib>
//------------------------------------------------------------------------------
namespace hpc {
#define ll long long
// 生地に関する情報
typedef struct {
CandidateLaneType lane;
int index;
int score;
} PInfo;
// 配置され、箱と化してしまった生地
typedef struct {
int x1; // 最小値
int y1;
int z1;
int x2; // 最大値
int y2;
int z2;
PInfo p;
} Box;
// 配置前の生地
typedef struct {
int width;
int height;
int depth; // 焼き時間+1
PInfo p;
} P;
float bestScore; // 試した中で最も良いスコア
int bestOrder[8]; // 試した中で最も良いスコアを出した順列
int lastLargeOrder[8]; // 前回の大きい生地の順列
int largeOrderSkipCount; // 次に大きい生地を配置するまでのターン数
int lastSmallOrder[8]; // 前回の小さい生地の順列
int smallOrderSkipCount; // 次に小さい生地を配置するまでのターン数
int turnCount; // 現在のステージ内のターン数#
int compCountLarge; // 大きい生地の順列を計算した回数
int compCountSmall; // 小さい生地の順列を計算した回数
bool stageNearsEnd; // ステージ終盤か?
bool stageNearsEndLarge; // 大きい生地用のステージ終盤フラグ
bool stageNearsEndSmall; // 小さい生地用のステージ終盤フラグ
int maxScoreEvaluateDepth; // 評価関数の有効範囲
// 次のBL点の候補のZ座標を探す
inline int nextZ(const Box *bs, int numBs, int z) {
int minZ = 10000;
for (int i = 0; i < numBs; i++) {
if (bs[i].z2 > z && bs[i].z2 < minZ) {
minZ = bs[i].z2;
}
}
return minZ;
}
// y方向のマスク用定数
const ll ymask3[8] = {
0x0000000000000000ll, 0x00000000001fffffll,
0x000003ffffe00000ll, 0x000003ffffffffffll,
0x7ffffc0000000000ll, 0x7ffffc00001fffffll,
0x7fffffffffe00000ll, 0x7fffffffffffffffll
};
// 21x21のbitboardの特定の範囲を1で埋める
static inline void fill(ll *board, int x, int y, int w, int h) {
// 21x21 bitboard
//
// 0 -- x+
// |
// y+
//
// index bit lower <- -> higher
// 0 1-21 000000000000000000000
// 0 22-42 000000000000000000000
// 0 43-63 000000000000000000000
// 1 1-21 000000000000000000000
// 1 22-42 000000000000000000000
// 1 43-63 000000000000000000000
// 2 1-21 000000000000000000000
// 2 22-42 000000000000000000000
// 2 43-63 000000000000000000000
// 3 1-21 000000000000000000000
// 3 22-42 000000000000000000000
// 3 43-63 000000000000000000000
// 4 1-21 000000000000000000000
// 4 22-42 000000000000000000000
// 4 43-63 000000000000000000000
// 5 1-21 000000000000000000000
// 5 22-42 000000000000000000000
// 5 43-63 000000000000000000000
// 6 1-21 000000000000000000000
// 6 22-42 000000000000000000000
// 6 43-63 000000000000000000000
ll xmask = (1ll << (x + w)) - (1ll << x);
ll ymask = (1ll << (y + h)) - (1ll << y);
xmask = xmask | xmask << 21 | xmask << 42;
board[0] |= xmask & ymask3[ymask & 7];
board[1] |= xmask & ymask3[ymask >> 3 & 7];
board[2] |= xmask & ymask3[ymask >> 6 & 7];
board[3] |= xmask & ymask3[ymask >> 9 & 7];
board[4] |= xmask & ymask3[ymask >> 12 & 7];
board[5] |= xmask & ymask3[ymask >> 15 & 7];
board[6] |= xmask & ymask3[ymask >> 18 & 7];
}
// ビット数カウント
inline int popcount(ll t) {
t = (t & 0x5555555555555555ll) + (t >> 1 & 0x5555555555555555ll);
t = (t & 0x3333333333333333ll) + (t >> 2 & 0x3333333333333333ll);
t = (t & 0x0f0f0f0f0f0f0f0fll) + (t >> 4 & 0x0f0f0f0f0f0f0f0fll);
t = (t & 0x00ff00ff00ff00ffll) + (t >> 8 & 0x00ff00ff00ff00ffll);
t = (t & 0x0000ffff0000ffffll) + (t >> 16 & 0x0000ffff0000ffffll);
t = (t & 0x00000000ffffffffll) + (t >> 32 & 0x00000000ffffffffll);
return (int) t;
}
// 最初に出現する0の位置
inline int indexOfZero(ll t) {
return popcount(t ^ (t + 1)) - 1;
}
// 指定されたz座標の範囲でのBL点を見つける
void findBL(const Box *bs, int numBs, int z1, int z2, int w, int h, int &xOut, int &yOut) {
ll board[7];
board[0] = 0;
board[1] = 0;
board[2] = 0;
board[3] = 0;
board[4] = 0;
board[5] = 0;
board[6] = 0;
// 壁のNFPを埋める
fill(board, 21 - w, 0, w, 21);
fill(board, 0, 21 - h, 21, h);
// z方向に重なっている箱のNFPを埋める
for (int i = 0; i < numBs; i++) {
const Box &b = bs[i];
if (b.z1 < z2 && z1 < b.z2) {
// nfpX, nfpY は負になりうる
int nfpX = b.x1 + 1 - w;
int nfpY = b.y1 + 1 - h;
// 負になったら0にする
nfpX &= -(~nfpX >> 31 & 1);
nfpY &= -(~nfpY >> 31 & 1);
int nfpW = b.x2 - nfpX;
int nfpH = b.y2 - nfpY;
// NFPを埋める
ll xmask = (1ll << (nfpX + nfpW)) - (1ll << nfpX);
ll ymask = (1ll << (nfpY + nfpH)) - (1ll << nfpY);
xmask = xmask | xmask << 21 | xmask << 42;
board[0] |= xmask & ymask3[ymask & 7];
board[1] |= xmask & ymask3[ymask >> 3 & 7];
board[2] |= xmask & ymask3[ymask >> 6 & 7];
board[3] |= xmask & ymask3[ymask >> 9 & 7];
board[4] |= xmask & ymask3[ymask >> 12 & 7];
board[5] |= xmask & ymask3[ymask >> 15 & 7];
board[6] |= xmask & ymask3[ymask >> 18 & 7];
}
}
// 最初に0になっているビットを発見して場所を計算する
for (int i = 0; i < 7; i++) {
ll b = board[i];
if (b != 0x7fffffffffffffffll) {
int x = indexOfZero(b);
xOut = x % 21;
yOut = x / 21 + i * 3;
return;
}
}
xOut = -1;
yOut = -1;
}
// 生地を配置する場所を探索する
void findPos(Box *bs, int &numBs, const P &p, bool *ng, int &xOut, int &yOut, int &zOut, int pieceIndex, int numPuts) {
int z = 0;
while (true) {
// z値チェック
while (ng[z]) {
z++; // 既に置いてある
}
int posX = 0;
int posY = 0;
// BL点を探す
findBL(bs, numBs, z, z + p.depth, p.width, p.height, posX, posY);
// 置ける場所が見つかったか?
if (posX != -1) {
xOut = posX;
yOut = posY;
zOut = z;
return;
}
z = nextZ(bs, numBs, z); // 次のzを探す
}
}
// 置かれたピースから終盤のスコアを計算する
inline float computeScoreNearEnd(const Box *bs, int numBs, int evaluateLastNPieces) {
// 末尾から evaluateLastNPieces 個だけを評価
float score = 0;
for (int i = numBs - evaluateLastNPieces; i < numBs; i++) {
const Box &b = bs[i];
if (b.z2 + turnCount - 1 <= 1000) { // 1000ターン以内に焼きあがるものだけ加算
score += b.p.score;
}
}
// 生地が収まり切らなかったときに終盤と判定されるが、生地が全て収まる方が好ましいためスコアを小さくする
return score * 0.01f;
}
// 特定の範囲にあるスコアを計算する
inline float computeScoreIn(const Box *bs, int numBs, int minZ, int maxZ, int evaluateLastNPieces) {
float res = 0;
// 末尾から evaluateLastNPieces 個だけを評価
for (int i = numBs - evaluateLastNPieces; i < numBs; i++) {
const Box &b = bs[i];
int z1 = b.z1;
int z2 = b.z2;
if (z2 + turnCount - 1 <= 1000 && z1 < maxZ && minZ < z2) { // 1000ターン以内に焼き上がり、かつ一部でも重なっていたら
if (z1 < minZ) z1 = minZ;
if (z2 > maxZ) z2 = maxZ;
// スコアの最大化は充填率の最大化と見なせるため、体積をスコアとする
int volume = (b.x2 - b.x1) * (b.y2 - b.y1) * (z2 - z1);
res += volume;
}
}
return res;
}
// 置かれたピースからこの先見込めるスコアを計算する
float computeScore(const Box *bs, int numBs, int evaluateLastNPieces, bool nearEnd) {
if (nearEnd) {
return computeScoreNearEnd(bs, numBs, evaluateLastNPieces); // ステージ終盤のスコア計算を行う
}
#define NUM_DIV 6
int maxZ = maxScoreEvaluateDepth;
int zs[NUM_DIV + 1];
float ws[NUM_DIV];
for (int i = 0; i < NUM_DIV; i++) {
float t = 1.0f - i * 1.0f / NUM_DIV;
zs[i + 1] = maxZ * (i + 1) / NUM_DIV; // 0~maxZをNUM_DIV分割し、
ws[i] = t; // 重みを設定する
}
float score = 0;
for (int i = 0; i < NUM_DIV; i++) {
// 分割された各範囲に対して重みを付けて加算
score += computeScoreIn(bs, numBs, zs[i], zs[i + 1], evaluateLastNPieces) * ws[i];
}
return score;
#undef NUM_DIV
}
void tryBruteImpl(Box *bs, int &numBs, const P *ps, int numPs, bool* ng, int *pieceToOrder, int idx, int numPuts, int prevZ, int maxDepthToBrute, int unableToPutCount) {
bool seemsToBeUnableToPutPiecesAnyMore = unableToPutCount >= 3; // 3つ以上生地が置けなければもう置けないと判断して停止
bool allPiecesPut = idx == numPs; // 全部置いたら停止
if (allPiecesPut || seemsToBeUnableToPutPiecesAnyMore) {
bool nearEnd = unableToPutCount >= 3; // 3つ以上置けなかった場合はステージ終盤と見なす
stageNearsEnd |= nearEnd; // ステージ終盤に突入したフラグを立てる
float score = computeScore(bs, numBs, numPuts, nearEnd); // 暫定スコア計算
// 最高スコアの更新処理
if (score > bestScore) {
bestScore = score;
// pieceToOrder を orderToPiece に変換する
// 実際に配置するピースは8個より少なくても
// インデックスが0~7まであるのでループは8回回す必要がある
int orderToPiece[8];
for (int i = 0; i < 8; i++) {
orderToPiece[i] = -1;
}
for (int i = 0; i < 8; i++) {
if (pieceToOrder[i] != -1) orderToPiece[pieceToOrder[i]] = i;
}
// 順列更新
for (int i = 0; i < 8; i++) {
bestOrder[i] = orderToPiece[i];
}
}
return;
}
// 全探索中か?
bool bruteSearch = idx < maxDepthToBrute;
// idx番目に置くピースを選ぶ
for (int i = 0; i < numPs; i++) {
int pieceIndex = i;
const P &p = ps[pieceIndex];
if (pieceToOrder[pieceIndex] != -1) continue; // 使用済み
// 次置ける場所を探す
int x = 0;
int y = 0;
int z = 0;
findPos(bs, numBs, p, ng, x, y, z, pieceIndex, numPuts);
bool doPut = z + p.depth + turnCount - 1 <= 1000; // 制限を突破したら置かない
if (doPut) {
// 試しに置く
pieceToOrder[pieceIndex] = numPuts;
ng[z] = true;
bs[numBs].x1 = x;
bs[numBs].y1 = y;
bs[numBs].z1 = z;
bs[numBs].x2 = x + p.width;
bs[numBs].y2 = y + p.height;
bs[numBs].z2 = z + p.depth;
bs[numBs].p = p.p;
numBs++;
numPuts++;
} else {
// 置けなかった
pieceToOrder[pieceIndex] = -1;
unableToPutCount++;
}
// 再帰的に計算
int nextZ = doPut ? z : prevZ; // ※最終的にはこの変数は使われなかった
tryBruteImpl(bs, numBs, ps, numPs, ng, pieceToOrder, idx + 1, numPuts, nextZ, maxDepthToBrute, unableToPutCount);
if (doPut) {
// 元に戻す
pieceToOrder[pieceIndex] = -1;
ng[z] = false;
numBs--;
numPuts--;
} else {
unableToPutCount--;
}
if (!bruteSearch) break; // 許容深さを超えたら全探索しない
}
}
// 指定の深さまでの全探索で最適な配置を計算する
inline void tryBrute(Box *bs, int &numBs, const P *ps, int numPs, bool* ng, int maxDepthToBrute) {
int order[8];
for (int i = 0; i < 8; i++) order[i] = -1;
tryBruteImpl(bs, numBs, ps, numPs, ng, order, 0, 0, 0, maxDepthToBrute, 0);
}
// 事前計算された順列に従って生地を配置する
void putByOrder(Box *bs, int &numBs, P *ps, int numPs, bool* ng, const int *orderToPiece) {
int numPuts = 0;
for (int i = 0; i < numPs; i++) {
int pieceIndex = orderToPiece[i];
if (pieceIndex == -1) continue; // i番目には無が置かれた
P &p = ps[pieceIndex]; // i番目に置かれたのは pieceIndex 番目のピース
// 次置ける場所を探す
int x = 0;
int y = 0;
int z = 0;
findPos(bs, numBs, p, ng, x, y, z, pieceIndex, numPuts);
// 置く
numPuts++;
ng[z] = true;
bs[numBs].x1 = x;
bs[numBs].y1 = y;
bs[numBs].z1 = z;
bs[numBs].x2 = x + p.width;
bs[numBs].y2 = y + p.height;
bs[numBs].z2 = z + p.depth;
bs[numBs].p = p.p;
numBs++;
}
}
Answer::Answer() {
}
Answer::~Answer() {
}
void Answer::init(const Stage& aStage) {
largeOrderSkipCount = 0;
smallOrderSkipCount = 0;
// 初期の順列
for (int i = 0; i < 8; i++) {
lastLargeOrder[i] = i;
lastSmallOrder[i] = i;
}
turnCount = 0;
compCountLarge = 0;
compCountSmall = 0;
stageNearsEnd = false;
stageNearsEndLarge = false;
stageNearsEndSmall = false;
}
Action Answer::decideNextAction(const Stage& aStage) {
typedef Vector2i v2;
auto slane = CandidateLaneType_Small;
auto llane = CandidateLaneType_Large;
const CandidatePieces &sps = aStage.candidateLane(slane).pieces();
const CandidatePieces &lps = aStage.candidateLane(llane).pieces();
const Oven &ov = aStage.oven();
Box bs[20 * 20 + 8 + 8 + 10]; // 空間内の箱
int numBs = 0; // 配置済みの箱の数
P lb[8]; // 手持ちの大きい生地
P sb[8]; // 手持ちの小さい生地
turnCount++;
// オーブンに既にあるやつを調べる
{
const OvenedPieces &ovps = ov.bakingPieces();
int num = ovps.count();
for (int i = 0; i < num; i++) {
const Piece &p = ovps[i];
v2 pos = p.pos();
int width = p.width();
int height = p.height();
int depth = p.restRequiredHeatTurnCount() + 1; // 焼きあがってから回収までに1ターンかかる
bs[numBs].x1 = pos.x;
bs[numBs].y1 = pos.y;
bs[numBs].z1 = 0;
bs[numBs].x2 = pos.x + width;
bs[numBs].y2 = pos.y + height;
bs[numBs].z2 = depth;
bs[numBs].p.index = -1;
bs[numBs].p.score = 0;
numBs++;
}
}
// 手持ちを調べる
{
for (int i = 0; i < 8; i++) {
const Piece &p = lps[i];
lb[i].width = p.width();
lb[i].height = p.height();
lb[i].depth = p.requiredHeatTurnCount() + 1;
lb[i].p.lane = llane;
lb[i].p.index = i;
lb[i].p.score = p.score();
}
for (int i = 0; i < 8; i++) {
const Piece &p = sps[i];
sb[i].width = p.width();
sb[i].height = p.height();
sb[i].depth = p.requiredHeatTurnCount() + 1;
sb[i].p.lane = slane;
sb[i].p.index = i;
sb[i].p.score = p.score();
}
}
bool ng[1000]; // 1ターンに生地を1枚しか置けないため、既に置いたzはtrueにしておく
for (int i = 0; i < 1000; i++) ng[i] = false;
// ------------------------------------------------------------------
// 大きい生地の計算
// ------------------------------------------------------------------
bool doPutSmall = true;
maxScoreEvaluateDepth = 64; // スコアが高くなるよう調整
{
// スコアと順列初期化
bestScore = 0; for (int i = 0; i < 8; i++) bestOrder[i] = -1;
if (largeOrderSkipCount > 0) {
// 大きい生地に変化なし、前回のを使う
putByOrder(bs, numBs, lb, 8, ng, lastLargeOrder);
largeOrderSkipCount--;
} else {
// 時間内に収まるように深さを調整しつつ探索
int bruteSearchDepth;
if (stageNearsEndLarge) {
// 最後は深さ5で探索
bruteSearchDepth = 5;
} else {
// 4回に1回は深さ5で探索
bruteSearchDepth = compCountLarge % 4 == 0 ? 5 : 4;
}
stageNearsEnd = false;
tryBrute(bs, numBs, lb, 8, ng, bruteSearchDepth);
putByOrder(bs, numBs, lb, 8, ng, bestOrder);
stageNearsEndLarge |= stageNearsEnd;
// 順番を記憶
for (int i = 0; i < 8; i++) {
lastLargeOrder[i] = bestOrder[i];
}
// 最も下にある箱を探す
int minZ = 10000000;
for (int i = 0; i < numBs; i++) {
Box &b = bs[i];
if (b.p.index != -1 && b.z1 < minZ) {
minZ = b.z1;
}
}
// 今後minZ回は新しい生地が補給されない
largeOrderSkipCount = minZ;
// 小さい生地も配置し直す
smallOrderSkipCount = 0;
compCountLarge++;
}
}
// ------------------------------------------------------------------
// 小さい生地の計算
// ------------------------------------------------------------------
maxScoreEvaluateDepth = 46; // スコアが高くなるよう調整
if (doPutSmall) {
// スコアと順列初期化
bestScore = 0; for (int i = 0; i < 8; i++) bestOrder[i] = -1;
if (smallOrderSkipCount > 0) {
// 小さい生地に変化なし、前回のを使う
putByOrder(bs, numBs, sb, 8, ng, lastSmallOrder);
smallOrderSkipCount--;
} else {
// 時間内に収まるように深さを調整しつつ探索
int bruteSearchDepth;
if (stageNearsEndSmall) {
// 最後は深さ5で探索
bruteSearchDepth = 5;
} else {
// 4回に1回は深さ3で探索
bruteSearchDepth = compCountSmall % 4 == 0 ? 3 : 2;
}
stageNearsEnd = false;
tryBrute(bs, numBs, sb, 8, ng, bruteSearchDepth);
putByOrder(bs, numBs, sb, 8, ng, bestOrder);
stageNearsEndSmall |= stageNearsEnd;
// 順番を記憶
for (int i = 0; i < 8; i++) {
lastSmallOrder[i] = bestOrder[i];
}
// 最も下にある箱を探す
int minZ = 10000000;
for (int i = 0; i < numBs; i++) {
Box &b = bs[i];
if (b.p.index != -1 && b.z1 < minZ) {
minZ = b.z1;
}
}
// 今後minZ回は新しい生地が補給されない
smallOrderSkipCount = minZ;
compCountSmall++;
}
}
Action action = Action::Wait();
// 今のターンに置くべき生地を探す
for (int i = 0; i < numBs; i++) {
Box &b = bs[i];
if (b.z1 == 0 && b.p.index != -1) { // z座標が0の生地があれば、それが今のターンに置くべき生地
action = Action::Put(b.p.lane, b.p.index, v2(b.x1, b.y1));
break;
}
}
return action;
}
void Answer::finalize(const Stage& aStage) {
}
} // namespace
// EOF