00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032 #include <algorithm>
00033 #include <vector>
00034
00035 #include "base/commandlineflags.h"
00036 #include "base/commandlineflags.h"
00037 #include "base/integral_types.h"
00038 #include "base/concise_iterator.h"
00039 #include "base/map-util.h"
00040 #include "constraint_solver/constraint_solveri.h"
00041 #include "util/bitset.h"
00042 #include "base/random.h"
00043
00044 DEFINE_int32(symbols_per_card, 8, "Number of symbols per card.");
00045 DEFINE_int32(ls_seed, 1, "Seed for the random number generator (used by "
00046 "the Local Neighborhood Search).");
00047 DEFINE_bool(use_filter, true, "Use filter in the local search to prune moves.");
00048 DEFINE_int32(num_swaps, 4, "If num_swap > 0, the search for an optimal "
00049 "solution will be allowed to use an operator that swaps the "
00050 "symbols of up to num_swap pairs ((card1, symbol on card1), "
00051 "(card2, symbol on card2)).");
00052 DEFINE_int32(time_limit_in_ms,
00053 60000,
00054 "Time limit for the global search in ms.");
00055
00056 namespace operations_research {
00057
00058
00059
00060
00061
00062
00063 class SymbolsSharedByTwoCardsConstraint : public Constraint {
00064 public:
00065
00066 SymbolsSharedByTwoCardsConstraint(Solver* const solver,
00067 const std::vector<IntVar*>& card1_symbol_vars,
00068 const std::vector<IntVar*>& card2_symbol_vars,
00069 IntVar* const num_symbols_in_common_var)
00070 : Constraint(solver),
00071 card1_symbol_vars_(card1_symbol_vars),
00072 card2_symbol_vars_(card2_symbol_vars),
00073 num_symbols_(card1_symbol_vars.size()),
00074 num_symbols_in_common_var_(num_symbols_in_common_var) {
00075
00076 CHECK_EQ(card1_symbol_vars.size(), card2_symbol_vars.size());
00077
00078
00079 for (int i = 0; i < num_symbols_; ++i) {
00080 CHECK_GE(card1_symbol_vars_[i]->Min(), 0);
00081 CHECK_GE(card2_symbol_vars_[i]->Min(), 0);
00082 CHECK_LE(card1_symbol_vars_[i]->Max(), 1);
00083 CHECK_LE(card2_symbol_vars_[i]->Max(), 1);
00084 }
00085 }
00086
00087 virtual ~SymbolsSharedByTwoCardsConstraint() {}
00088
00089
00090
00091
00092 virtual void Post() {
00093
00094
00095
00096
00097
00098
00099 Demon* const global_demon =
00100 solver()->MakeDelayedConstraintInitialPropagateCallback(this);
00101
00102 for (int i = 0; i < num_symbols_; ++i) {
00103 card1_symbol_vars_[i]->WhenBound(global_demon);
00104 card2_symbol_vars_[i]->WhenBound(global_demon);
00105 }
00106
00107 num_symbols_in_common_var_->WhenBound(global_demon);
00108 }
00109
00110
00111
00112
00113
00114
00115
00116
00117
00118
00119
00120
00121
00122
00123
00124
00125
00126
00127
00128 virtual void InitialPropagate() {
00129 int max_symbols_in_common = 0;
00130 int min_symbols_in_common = 0;
00131 for (int i = 0; i < num_symbols_; ++i) {
00132 if (card1_symbol_vars_[i]->Min() == 1 &&
00133 card2_symbol_vars_[i]->Min() == 1) {
00134 min_symbols_in_common++;
00135 }
00136 if (card1_symbol_vars_[i]->Max() == 1 &&
00137 card2_symbol_vars_[i]->Max() == 1) {
00138 max_symbols_in_common++;
00139 }
00140 }
00141 num_symbols_in_common_var_->SetRange(min_symbols_in_common,
00142 max_symbols_in_common);
00143
00144
00145
00146 if (min_symbols_in_common == max_symbols_in_common) {
00147 DCHECK_EQ(min_symbols_in_common, num_symbols_in_common_var_->Max());
00148 DCHECK_EQ(min_symbols_in_common, num_symbols_in_common_var_->Min());
00149 return;
00150 }
00151 if (num_symbols_in_common_var_->Max() == min_symbols_in_common) {
00152
00153 for (int i = 0; i < num_symbols_; ++i) {
00154
00155
00156
00157
00158 if (card1_symbol_vars_[i]->Min() == 1 &&
00159 card2_symbol_vars_[i]->Min() == 0) {
00160 card2_symbol_vars_[i]->SetValue(0);
00161 } else if (card2_symbol_vars_[i]->Min() == 1 &&
00162 card1_symbol_vars_[i]->Min() == 0) {
00163 card1_symbol_vars_[i]->SetValue(0);
00164 }
00165 }
00166 } else if (num_symbols_in_common_var_->Min() == max_symbols_in_common) {
00167
00168 for (int i = 0; i < num_symbols_; ++i) {
00169 if (card1_symbol_vars_[i]->Max() == 1 &&
00170 card2_symbol_vars_[i]->Max() == 1) {
00171
00172
00173 card1_symbol_vars_[i]->SetValue(1);
00174 card2_symbol_vars_[i]->SetValue(1);
00175 }
00176 }
00177 }
00178 }
00179
00180 private:
00181 std::vector<IntVar*> card1_symbol_vars_;
00182 std::vector<IntVar*> card2_symbol_vars_;
00183 const int num_symbols_;
00184 IntVar* const num_symbols_in_common_var_;
00185 };
00186
00187
00188
00189
00190
00191
00192 IntVar* CreateViolationVar(Solver* const solver,
00193 const std::vector<IntVar*>& card1_symbol_vars,
00194 const std::vector<IntVar*>& card2_symbol_vars,
00195 int num_symbols_per_card) {
00196 IntVar* const num_symbols_in_common_var =
00197 solver->MakeIntVar(0, num_symbols_per_card);
00198
00199 solver->AddConstraint(
00200 solver->RevAlloc(
00201 new SymbolsSharedByTwoCardsConstraint(solver,
00202 card1_symbol_vars,
00203 card2_symbol_vars,
00204 num_symbols_in_common_var)));
00205 return solver->MakeAbs(solver->MakeSum(num_symbols_in_common_var, -1))->Var();
00206 }
00207
00208
00209
00210
00211
00212
00213
00214
00215
00216
00217
00218
00219
00220
00221
00222
00223
00224
00225
00226
00227
00228
00229
00230
00231
00232
00233
00234
00235
00236
00237
00238 class DobbleOperator : public IntVarLocalSearchOperator {
00239 public:
00240 DobbleOperator(const IntVar* const* card_symbol_vars,
00241 int num_vars,
00242 int num_cards,
00243 int num_symbols,
00244 int num_symbols_per_card)
00245 : IntVarLocalSearchOperator(card_symbol_vars, num_vars),
00246 num_cards_(num_cards),
00247 num_symbols_(num_symbols),
00248 num_symbols_per_card_(num_symbols_per_card),
00249 symbols_per_card_(num_cards) {
00250 CHECK_GT(num_cards, 0);
00251 CHECK_GT(num_vars, 0);
00252 CHECK_GT(num_symbols, 0);
00253 CHECK_GT(num_symbols_per_card, 0);
00254 for (int card = 0; card < num_cards; ++card) {
00255 symbols_per_card_[card].assign(num_symbols_per_card, -1);
00256 }
00257 }
00258
00259 virtual ~DobbleOperator() {}
00260
00261 protected:
00262
00263
00264
00265 virtual void OnStart() {
00266 for (int card = 0; card < num_cards_; ++card) {
00267 int found = 0;
00268 for (int symbol = 0; symbol < num_symbols_; ++symbol) {
00269 if (Value(VarIndex(card, symbol)) == 1) {
00270 symbols_per_card_[card][found++] = symbol;
00271 }
00272 }
00273 DCHECK_EQ(num_symbols_per_card_, found);
00274 }
00275 InitNeighborhoodSearch();
00276 }
00277
00278 virtual void InitNeighborhoodSearch() = 0;
00279
00280
00281
00282 int VarIndex(int card, int symbol) {
00283 return card * num_symbols_ + symbol;
00284 }
00285
00286
00287 void SwapTwoSymbolsOnCards(int card1, int symbol1, int card2, int symbol2) {
00288 SetValue(VarIndex(card1, symbol1), 0);
00289 SetValue(VarIndex(card2, symbol2), 0);
00290 SetValue(VarIndex(card1, symbol2), 1);
00291 SetValue(VarIndex(card2, symbol1), 1);
00292 }
00293
00294 const int num_cards_;
00295 const int num_symbols_;
00296 const int num_symbols_per_card_;
00297 std::vector<std::vector<int> > symbols_per_card_;
00298 };
00299
00300
00301
00302
00303
00304
00305
00306
00307
00308
00309 class SwapSymbols : public DobbleOperator {
00310 public:
00311 SwapSymbols(const IntVar* const* card_symbol_vars,
00312 int num_vars,
00313 int num_cards,
00314 int num_symbols,
00315 int num_symbols_per_card)
00316 : DobbleOperator(card_symbol_vars,
00317 num_vars,
00318 num_cards,
00319 num_symbols,
00320 num_symbols_per_card),
00321 current_card1_(-1),
00322 current_card2_(-1),
00323 current_symbol1_(-1),
00324 current_symbol2_(-1) {
00325 }
00326
00327 virtual ~SwapSymbols() {}
00328
00329
00330 virtual bool MakeOneNeighbor() {
00331 if (!PickNextSwap()) {
00332 VLOG(1) << "finished neighborhood";
00333 return false;
00334 }
00335
00336 const int symbol1 = symbols_per_card_[current_card1_][current_symbol1_];
00337 const int symbol2 = symbols_per_card_[current_card2_][current_symbol2_];
00338 SwapTwoSymbolsOnCards(current_card1_, symbol1, current_card2_, symbol2);
00339 return true;
00340 }
00341
00342 private:
00343
00344 virtual void InitNeighborhoodSearch() {
00345 current_card1_ = 0;
00346 current_card2_ = 1;
00347 current_symbol1_ = 0;
00348 current_symbol2_ = -1;
00349 }
00350
00351
00352 bool PickNextSwap() {
00353 current_symbol2_++;
00354 if (current_symbol2_ == num_symbols_per_card_) {
00355 current_symbol2_ = 0;
00356 current_symbol1_++;
00357 if (current_symbol1_ == num_symbols_per_card_) {
00358 current_symbol1_ = 0;
00359 current_card2_++;
00360 if (current_card2_ == num_cards_) {
00361 current_card1_++;
00362 current_card2_ = current_card1_ + 1;
00363 }
00364 }
00365 }
00366 return current_card1_ < num_cards_ - 1;
00367 }
00368
00369 int current_card1_;
00370 int current_card2_;
00371 int current_symbol1_;
00372 int current_symbol2_;
00373 };
00374
00375
00376
00377
00378
00379
00380
00381
00382
00383
00384
00385
00386 class SwapSymbolsOnCardPairs : public DobbleOperator {
00387 public:
00388 SwapSymbolsOnCardPairs(const IntVar* const* card_symbol_vars,
00389 int num_vars,
00390 int num_cards,
00391 int num_symbols,
00392 int num_symbols_per_card,
00393 int max_num_swaps)
00394 : DobbleOperator(card_symbol_vars,
00395 num_vars,
00396 num_cards,
00397 num_symbols,
00398 num_symbols_per_card),
00399 rand_(FLAGS_ls_seed),
00400 max_num_swaps_(max_num_swaps) {
00401 CHECK_GE(max_num_swaps, 2);
00402 }
00403
00404 virtual ~SwapSymbolsOnCardPairs() {}
00405
00406 protected:
00407 virtual bool MakeOneNeighbor() {
00408 const int num_swaps = rand_.Uniform(max_num_swaps_ - 1) + 2;
00409 for (int i = 0; i < num_swaps; ++i) {
00410 const int card_1 = rand_.Uniform(num_cards_);
00411 const int symbol_index_1 = rand_.Uniform(num_symbols_per_card_);
00412 const int symbol_1 = symbols_per_card_[card_1][symbol_index_1];
00413 const int card_2 = rand_.Uniform(num_cards_);
00414 const int symbol_index_2 = rand_.Uniform(num_symbols_per_card_);
00415 const int symbol_2 = symbols_per_card_[card_2][symbol_index_2];
00416 SwapTwoSymbolsOnCards(card_1, symbol_1, card_2, symbol_2);
00417 }
00418 return true;
00419 }
00420
00421 virtual void InitNeighborhoodSearch() {}
00422
00423 private:
00424 ACMRandom rand_;
00425 const int max_num_swaps_;
00426 };
00427
00428
00429
00430
00431
00432
00433
00434
00435
00436
00437
00438
00439
00440
00441
00442
00443
00444
00445
00446
00447
00448
00449
00450
00451
00452
00453
00454
00455 class DobbleFilter : public IntVarLocalSearchFilter {
00456 public:
00457 DobbleFilter(const IntVar* const* card_symbol_vars,
00458 int num_vars,
00459 int num_cards,
00460 int num_symbols,
00461 int num_symbols_per_card)
00462 : IntVarLocalSearchFilter(card_symbol_vars, num_vars),
00463 num_cards_(num_cards),
00464 num_symbols_(num_symbols),
00465 num_symbols_per_card_(num_symbols_per_card),
00466 temporary_bitset_(0),
00467 symbol_bitmask_per_card_(num_cards, 0),
00468 violation_costs_(num_cards_, std::vector<int>(num_cards_, 0)) {}
00469
00470
00471
00472 virtual void OnSynchronize() {
00473 symbol_bitmask_per_card_.assign(num_cards_, 0);
00474 for (int card = 0; card < num_cards_; ++card) {
00475 for (int symbol = 0; symbol < num_symbols_; ++symbol) {
00476 if (Value(VarIndex(card, symbol))) {
00477 SetBit64(&symbol_bitmask_per_card_[card], symbol);
00478 }
00479 }
00480 }
00481 for (int card1 = 0; card1 < num_cards_; ++card1) {
00482 for (int card2 = 0; card2 < num_cards_; ++card2) {
00483 violation_costs_[card1][card2] =
00484 ViolationCost(BitCount64(symbol_bitmask_per_card_[card1] &
00485 symbol_bitmask_per_card_[card2]));
00486 }
00487 }
00488 DCHECK(CheckCards());
00489 }
00490
00491
00492
00493
00494 virtual bool Accept(const Assignment* delta,
00495 const Assignment* unused_deltadelta) {
00496 const Assignment::IntContainer& solution_delta = delta->IntVarContainer();
00497 const int solution_delta_size = solution_delta.Size();
00498
00499
00500
00501
00502
00503
00504
00505
00506
00507
00508
00509
00510
00511
00512
00513
00514
00515
00516 for (int i = 0; i < solution_delta_size; ++i) {
00517 if (!solution_delta.Element(i).Activated()) {
00518 VLOG(1)
00519 << "Element #" << i << " of the delta assignment given to"
00520 << " DobbleFilter::Accept() is not activated (i.e. its variable"
00521 << " is not bound to a single value anymore). This means that"
00522 << " we are in a LNS phase, and the DobbleFilter won't be able"
00523 << " to filter anything. Returning true.";
00524 return true;
00525 }
00526 }
00527 VLOG(1) << "No LNS, size = " << solution_delta_size;
00528
00529
00530 std::vector<int> touched_cards;
00531 ComputeTouchedCards(solution_delta, &touched_cards);
00532
00533
00534 if (!CheckCards()) {
00535 RestoreBitsetPerCard();
00536 DCHECK(CheckCards());
00537 VLOG(1) << "reject by size";
00538 return false;
00539 }
00540
00541
00542 const int cost_delta = ComputeNewCost(touched_cards);
00543
00544
00545 RestoreBitsetPerCard();
00546
00547
00548
00549 if (cost_delta >= 0) {
00550 VLOG(1) << "reject";
00551 }
00552 return cost_delta < 0;
00553 }
00554
00555 private:
00556
00557 struct UndoChange {
00558 UndoChange(int c, uint64 b) : card(c), bitset(b) {}
00559 int card;
00560 uint64 bitset;
00561 };
00562
00563 int VarIndex(int card, int symbol) {
00564 return card * num_symbols_ + symbol;
00565 }
00566
00567 void ClearBitset() {
00568 temporary_bitset_ = 0;
00569 }
00570
00571
00572
00573
00574 int ComputeNewCost(const std::vector<int>& touched_cards) {
00575 ClearBitset();
00576 int cost_delta = 0;
00577 for (int i = 0; i < touched_cards.size(); ++i) {
00578 const int touched = touched_cards[i];
00579 SetBit64(&temporary_bitset_, touched);
00580 const uint64 card_bitset = symbol_bitmask_per_card_[touched];
00581 const std::vector<int>& row_cost = violation_costs_[touched];
00582 for (int other_card = 0; other_card < num_cards_; ++other_card) {
00583 if (!IsBitSet64(&temporary_bitset_, other_card)) {
00584 cost_delta +=
00585 ViolationCost(BitCount64(card_bitset &
00586 symbol_bitmask_per_card_[other_card]));
00587 cost_delta -= row_cost[other_card];
00588 }
00589 }
00590 }
00591 return cost_delta;
00592 }
00593
00594
00595 void ComputeTouchedCards(const Assignment::IntContainer& solution_delta,
00596 std::vector<int>* const touched_cards) {
00597 ClearBitset();
00598 const int solution_delta_size = solution_delta.Size();
00599 const int kUnassigned = -1;
00600 for (int index = 0; index < solution_delta_size; ++index) {
00601 int64 touched_var = kUnassigned;
00602 FindIndex(solution_delta.Element(index).Var(), &touched_var);
00603 CHECK_NE(touched_var, kUnassigned);
00604 const int card = touched_var / num_symbols_;
00605 const int symbol = touched_var % num_symbols_;
00606 const int new_value = solution_delta.Element(index).Value();
00607 if (!IsBitSet64(&temporary_bitset_, card)) {
00608 SaveRestoreInformation(card);
00609 touched_cards->push_back(card);
00610 SetBit64(&temporary_bitset_, card);
00611 }
00612 if (new_value) {
00613 SetBit64(&symbol_bitmask_per_card_[card], symbol);
00614 } else {
00615 ClearBit64(&symbol_bitmask_per_card_[card], symbol);
00616 }
00617 }
00618 }
00619
00620
00621 void RestoreBitsetPerCard() {
00622 for (int i = 0; i < restore_information_.size(); ++i) {
00623 symbol_bitmask_per_card_[restore_information_[i].card] =
00624 restore_information_[i].bitset;
00625 }
00626 restore_information_.clear();
00627 }
00628
00629
00630 void SaveRestoreInformation(int card) {
00631 restore_information_.push_back(UndoChange(card,
00632 symbol_bitmask_per_card_[card]));
00633 }
00634
00635
00636
00637 bool CheckCards() {
00638 for (int i = 0; i < num_cards_; ++i) {
00639 if (num_symbols_per_card_ != BitCount64(symbol_bitmask_per_card_[i])) {
00640 VLOG(1) << "card " << i << " has bitset of size "
00641 << BitCount64(symbol_bitmask_per_card_[i]);
00642 return false;
00643 }
00644 }
00645 return true;
00646 }
00647
00648 int ViolationCost(uint64 cardinality) const {
00649 return (cardinality > 0 ? cardinality - 1 : 1);
00650 }
00651
00652 const int num_cards_;
00653 const int num_symbols_;
00654 const int num_symbols_per_card_;
00655 uint64 temporary_bitset_;
00656 std::vector<uint64> symbol_bitmask_per_card_;
00657 std::vector<std::vector<int> > violation_costs_;
00658 std::vector<UndoChange> restore_information_;
00659 };
00660
00661
00662
00663 void SolveDobble(int num_cards, int num_symbols, int num_symbols_per_card) {
00664 LOG(INFO) << "Solving dobble assignment problem:";
00665 LOG(INFO) << " - " << num_cards << " cards";
00666 LOG(INFO) << " - " << num_symbols << " symbols";
00667 LOG(INFO) << " - " << num_symbols_per_card << " symbols per card";
00668
00669
00670 Solver solver("dobble");
00671
00672 std::vector<std::vector<IntVar*> > card_symbol_vars(num_cards);
00673 std::vector<IntVar*> all_card_symbol_vars;
00674 for (int card_index = 0; card_index < num_cards; ++card_index) {
00675 solver.MakeBoolVarArray(num_symbols,
00676 StringPrintf("card_%i_", card_index),
00677 &card_symbol_vars[card_index]);
00678 for (int symbol_index = 0;
00679 symbol_index < num_symbols;
00680 ++symbol_index) {
00681 all_card_symbol_vars.push_back(
00682 card_symbol_vars[card_index][symbol_index]);
00683 }
00684 }
00685
00686
00687 std::vector<IntVar*> violation_vars;
00688 for (int card1 = 0; card1 < num_cards; ++card1) {
00689 for (int card2 = 0; card2 < num_cards; ++card2) {
00690 if (card1 != card2) {
00691 violation_vars.push_back(CreateViolationVar(&solver,
00692 card_symbol_vars[card1],
00693 card_symbol_vars[card2],
00694 num_symbols_per_card));
00695 }
00696 }
00697 }
00698
00699 IntVar* const objective_var = solver.MakeSum(violation_vars)->Var();
00700
00701
00702
00703 for (int card = 0; card < num_cards; ++card) {
00704 solver.AddConstraint(solver.MakeSumEquality(card_symbol_vars[card],
00705 num_symbols_per_card));
00706 }
00707
00708
00709
00710
00711
00712
00713
00714 for (int symbol_index = 0; symbol_index < num_symbols; ++symbol_index) {
00715 std::vector<IntVar*> tmp;
00716 for (int card_index = 0; card_index < num_cards; ++card_index) {
00717 tmp.push_back(card_symbol_vars[card_index][symbol_index]);
00718 }
00719 solver.AddConstraint(solver.MakeSumEquality(tmp, num_symbols_per_card));
00720 }
00721
00722
00723 LOG(INFO) << "Solving with Local Search";
00724 LOG(INFO) << " - time limit = " << FLAGS_time_limit_in_ms << " ms";
00725
00726
00727
00728
00729 DecisionBuilder* const build_db = solver.MakePhase(
00730 all_card_symbol_vars,
00731 Solver::CHOOSE_RANDOM,
00732 Solver::ASSIGN_MAX_VALUE);
00733
00734
00735 std::vector<LocalSearchOperator*> operators;
00736 LocalSearchOperator* const switch_operator =
00737 solver.RevAlloc(new SwapSymbols(all_card_symbol_vars.data(),
00738 all_card_symbol_vars.size(),
00739 num_cards,
00740 num_symbols,
00741 num_symbols_per_card));
00742 operators.push_back(switch_operator);
00743 LOG(INFO) << " - add switch operator";
00744 if (FLAGS_num_swaps > 0) {
00745 LocalSearchOperator* const swaps_operator =
00746 solver.RevAlloc(new SwapSymbolsOnCardPairs(all_card_symbol_vars.data(),
00747 all_card_symbol_vars.size(),
00748 num_cards,
00749 num_symbols,
00750 num_symbols_per_card,
00751 FLAGS_num_swaps));
00752 operators.push_back(swaps_operator);
00753 LOG(INFO) << " - add swaps operator with at most "
00754 << FLAGS_num_swaps << " swaps";
00755 }
00756
00757
00758 std::vector<LocalSearchFilter*> filters;
00759 if (FLAGS_use_filter) {
00760 filters.push_back(solver.RevAlloc(
00761 new DobbleFilter(all_card_symbol_vars.data(),
00762 all_card_symbol_vars.size(),
00763 num_cards,
00764 num_symbols,
00765 num_symbols_per_card)));
00766 }
00767
00768
00769
00770
00771 DecisionBuilder* const final_db =
00772 solver.MakeLocalSearchPhase(
00773 all_card_symbol_vars,
00774 build_db,
00775 solver.MakeLocalSearchPhaseParameters(
00776 solver.ConcatenateOperators(operators, true),
00777 NULL,
00778 NULL,
00779
00780
00781 filters));
00782
00783
00784 std::vector<SearchMonitor*> monitors;
00785
00786 OptimizeVar* const optimize = solver.MakeMinimize(objective_var, 1);
00787 monitors.push_back(optimize);
00788
00789
00790 SearchMonitor* const log = solver.MakeSearchLog(100000, optimize);
00791 monitors.push_back(log);
00792
00793
00794 SearchLimit* const time_limit =
00795 solver.MakeLimit(FLAGS_time_limit_in_ms, kint64max, kint64max, kint64max);
00796 monitors.push_back(time_limit);
00797
00798
00799 solver.Solve(final_db, monitors);
00800 }
00801 }
00802
00803 int main(int argc, char **argv) {
00804 google::ParseCommandLineFlags(&argc, &argv, true);
00805
00806
00807 const int kSymbolsPerCard = FLAGS_symbols_per_card;
00808 const int kCards = kSymbolsPerCard * (kSymbolsPerCard - 1) + 1;
00809 const int kSymbols = kCards;
00810 operations_research::SolveDobble(kCards,
00811 kSymbols,
00812 kSymbolsPerCard);
00813 return 0;
00814 }