結果
| 問題 |
No.5005 3-SAT
|
| ユーザー |
|
| 提出日時 | 2025-04-14 00:59:53 |
| 言語 | C++17 (gcc 13.3.0 + boost 1.87.0) |
| 結果 |
AC
|
| 実行時間 | 1,902 ms / 2,000 ms |
| コード長 | 9,095 bytes |
| コンパイル時間 | 1,643 ms |
| コンパイル使用メモリ | 122,236 KB |
| 実行使用メモリ | 7,844 KB |
| スコア | 98,857 |
| 最終ジャッジ日時 | 2025-04-14 01:03:11 |
| 合計ジャッジ時間 | 196,980 ms |
|
ジャッジサーバーID (参考情報) |
judge4 / judge3 |
| 純コード判定しない問題か言語 |
(要ログイン)
| ファイルパターン | 結果 |
|---|---|
| other | AC * 100 |
ソースコード
#include <iostream>
#include <vector>
#include <array>
#include <chrono>
#include <random>
#include <algorithm>
#include <string>
// 条件を表す構造体(a, b, c: 桁番号 / p, q, r: 必要な値)
struct Condition {
int a, b, c, p, q, r;
};
// 時間計測クラス(開始時刻を保存し、指定の秒数を超えているか確認)
class TimeKeeper {
public:
TimeKeeper() : start(std::chrono::steady_clock::now()) {}
// LIMIT(秒)を超えているか
bool is_time_over(double limit) {
auto now = std::chrono::steady_clock::now();
std::chrono::duration<double> elapsed = now - start;
return elapsed.count() >= limit;
}
// 現在の経過時間(秒)を返す
double time_now() {
auto now = std::chrono::steady_clock::now();
return std::chrono::duration<double>(now - start).count();
}
private:
std::chrono::steady_clock::time_point start;
};
// Solver クラス:局所探索を用いて解を改善する
class Solver {
public:
int N; // 条件の数
int K; // 桁数
std::vector<Condition> X; // 条件リスト
// 付属情報:各桁 k で、0/1 を選んだときに達成される条件番号リスト(Python 版では set を用いている)
std::vector<std::array<std::vector<int>, 2>> C;
// ビット操作の際に使うランダム順の桁番号リスト
std::vector<int> indices;
int idx_ptr;
// 乱数生成器
std::mt19937 rng;
std::uniform_int_distribution<int> binary_dist;
Solver(int N_, int K_, const std::vector<Condition>& X_)
: N(N_), K(K_), X(X_), idx_ptr(0), rng(std::random_device{}()), binary_dist(0, 1)
{
// C の初期化:各桁について 0 と 1 の場合に達成される条件番号リストを作成
C.resize(K);
for (int k = 0; k < K; k++) {
C[k][0].clear();
C[k][1].clear();
}
for (int i = 0; i < N; i++) {
const auto& cond = X[i];
C[cond.a][cond.p].push_back(i);
C[cond.b][cond.q].push_back(i);
C[cond.c][cond.r].push_back(i);
}
// 事前に 0~K-1 の桁番号リストを作りランダムにシャッフル
indices.resize(K);
for (int i = 0; i < K; i++) {
indices[i] = i;
}
std::shuffle(indices.begin(), indices.end(), rng);
}
// 条件を先頭から評価し、条件が満たされなくなった時点でループを抜ける(部分スコア評価)
int calscore(const std::vector<int>& ans) {
int sc = 0;
for (int i = 0; i < N; i++) {
const auto& cond = X[i];
if (ans[cond.a] == cond.p || ans[cond.b] == cond.q || ans[cond.c] == cond.r) {
sc++;
} else {
return sc;
}
}
return sc;
}
// 全条件に対してスコアを計算する(全評価)
int calscore_full(const std::vector<int>& ans) {
int sc = 0;
for (int i = 0; i < N; i++) {
const auto& cond = X[i];
if (ans[cond.a] == cond.p || ans[cond.b] == cond.q || ans[cond.c] == cond.r)
sc++;
}
return sc;
}
// グリーディー法による初期解生成(各条件を順番にチェックして未定なら設定)
std::pair<std::vector<int>, int> initial_solution() {
std::vector<int> ans(K, -1);
for (int i = 0; i < N; i++) {
const auto& cond = X[i];
if ( (ans[cond.a] == cond.p) || (ans[cond.b] == cond.q) || (ans[cond.c] == cond.r) )
continue;
if (ans[cond.a] == -1)
ans[cond.a] = cond.p;
else if (ans[cond.b] == -1)
ans[cond.b] = cond.q;
else if (ans[cond.c] == -1)
ans[cond.c] = cond.r;
}
for (int k = 0; k < K; k++) {
if (ans[k] == -1)
ans[k] = binary_dist(rng);
}
int sc = calscore(ans);
return {ans, sc};
}
// 別バリエーションの初期解生成:条件ごとに利用可能な候補からランダムに一つ選ぶ
std::pair<std::vector<int>, int> initial_solution2() {
std::vector<int> ans(K, -1);
for (int i = 0; i < N; i++) {
const auto& cond = X[i];
if ( (ans[cond.a] == cond.p) || (ans[cond.b] == cond.q) || (ans[cond.c] == cond.r) )
continue;
std::vector<std::pair<int, int>> candidates;
if (ans[cond.a] == -1)
candidates.push_back({cond.a, cond.p});
if (ans[cond.b] == -1)
candidates.push_back({cond.b, cond.q});
if (ans[cond.c] == -1)
candidates.push_back({cond.c, cond.r});
if (!candidates.empty()) {
std::uniform_int_distribution<int> cand_dist(0, static_cast<int>(candidates.size() - 1));
auto choice = candidates[cand_dist(rng)];
ans[choice.first] = choice.second;
}
}
for (int k = 0; k < K; k++) {
if (ans[k] == -1)
ans[k] = binary_dist(rng);
}
int sc = calscore(ans);
return {ans, sc};
}
// 複数の初期解候補を生成し、最もスコアが高い解を選択する
std::pair<std::vector<int>, int> multi_initial_solution(int num_candidates = 100) {
std::vector<int> best_ans;
int best_sc = -1;
for (int i = 0; i < num_candidates; i++) {
auto candidate = initial_solution2();
if (candidate.second > best_sc) {
best_ans = candidate.first;
best_sc = candidate.second;
}
}
return {best_ans, best_sc};
}
// 事前生成したランダムな桁番号リストから次の桁を取得(全体を使い切れば再シャッフル)
int get_random_index() {
if (idx_ptr >= K) {
std::shuffle(indices.begin(), indices.end(), rng);
idx_ptr = 0;
}
return indices[idx_ptr++];
}
// 局所探索:各ビット反転を試み、改善があれば更新するヒルクライミング法
std::pair<std::vector<int>, int> solve(TimeKeeper &tk, double LIMIT) {
// 初期解候補(候補数は 1 で十分)
int n_iter = 1;
auto init = multi_initial_solution(n_iter);
std::vector<int> current_ans = init.first;
int current_sc = init.second;
std::vector<int> best_ans = current_ans;
int best_sc = current_sc;
int loop = 0;
while (!tk.is_time_over(LIMIT)) {
loop++;
bool improved = false;
// 全桁に対して反転を試みる
for (int i = 0; i < K; i++) {
int k = get_random_index();
// ビット反転(in-place)
current_ans[k] = 1 - current_ans[k];
int new_sc = calscore(current_ans);
int delta = new_sc - current_sc;
if (delta >= 0) {
current_sc = new_sc;
if (current_sc > best_sc) {
std::cerr << "best: loop=" << loop << " current_sc=" << current_sc << "\n";
best_sc = current_sc;
best_ans = current_ans; // 解のコピー
}
improved = true;
break; // 1ビット反転で改善が見られた場合、次の外側ループへ
} else {
// 改善がなければ反転を元に戻す
current_ans[k] = 1 - current_ans[k];
}
}
if (!improved) {
std::cerr << "not improved\n";
break;
}
}
return {best_ans, best_sc};
}
};
int main(int argc, char* argv[]) {
// コマンドライン引数に "--debug" があれば DEBUG モードとする
bool DEBUG = false;
for (int i = 1; i < argc; i++) {
if (std::string(argv[i]) == "--debug") {
DEBUG = true;
}
}
// 時間計測用オブジェクトの生成
TimeKeeper tk;
double LIMIT = DEBUG ? 0.5 : 1.9;
// 入力値:条件の数 2048, 桁数 256 として固定
constexpr int N = 2048;
constexpr int K = 256;
std::vector<Condition> X;
X.reserve(N);
for (int i = 0; i < N; i++) {
int a, b, c, p, q, r;
std::cin >> a >> b >> c >> p >> q >> r;
X.push_back({a, b, c, p, q, r});
}
// Solver の生成と解探索
Solver solver(N, K, X);
auto result = solver.solve(tk, LIMIT);
std::vector<int> ans = result.first;
int sc = solver.calscore(ans);
// 出力:桁を逆順に結合して 1 行で出力
for (int i = K - 1; i >= 0; i--) {
std::cout << ans[i];
}
std::cout << "\n";
std::cerr << "SC " << sc << "\n";
return 0;
}