Advertisement
Guest User

Untitled

a guest
Jun 18th, 2019
79
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 7.76 KB | None | 0 0
  1. // Compile and run with:
  2. //
  3. // $ g++ -Wall -Wconversion tournament.cc -g -O3 -o tournament --std=c++17 && time ./tournament
  4.  
  5. #include <numeric>
  6. #include <limits>
  7. #include <map>
  8. #include <vector>
  9. #include <iostream>
  10. #include <algorithm>
  11. // Uncomment the next line, and the line in main() if you have Boost libraries installed.
  12. // #include <boost/rational.hpp>
  13.  
  14. using namespace std;
  15.  
  16. constexpr int factorial(int n) {
  17. return n <= 1 ? 1 : (n * factorial(n-1));
  18. }
  19.  
  20. const int K = 5; // Number of players.
  21. const int N = 8; // Number of matches.
  22. const int KX = factorial(K);
  23.  
  24. // state[i][j] = how many times player i won against player j.
  25. using state_t = array<array<int, K>, K>;
  26.  
  27. // cache_key_t is a compact representation of state_t.
  28. using cache_key_t = long long;
  29.  
  30. // entry is the best strategy found for the given state_t
  31. struct entry {
  32. double prob; // Probability of success.
  33. int i, j; // Which players should play against each other next.
  34. // If j < 0, then nobody plays, just guess player i.
  35. };
  36.  
  37. // Stores all strategies computed so far. Crucial for speed.
  38. map<cache_key_t, entry> cache;
  39.  
  40. // List of all permutations of K elements.
  41. vector<array<int, K>> perms;
  42.  
  43. // Transforms state_t into the corresponding cache_key_t.
  44. // This results in ~5x speedup.
  45. cache_key_t StateKey(const state_t &state) {
  46. static_assert(K*(K-1) + N < std::numeric_limits<cache_key_t>::digits,
  47. "K and N too large for cache key");
  48. cache_key_t key = 0;
  49. for (int i = 0; i < K; ++i)
  50. for (int j = 0; j < K; ++j)
  51. if (i != j) {
  52. key <<= 1;
  53. key |= 1;
  54. key <<= state[i][j];
  55. }
  56. return key;
  57. }
  58.  
  59. // Looks up the given state in cache. Because of problem symmetry, it
  60. // tries all possible K! state permutations. Trying all permutations
  61. // results in ~10x speedup.
  62. bool FindInCache(state_t state, entry* entry) {
  63. for (const auto& perm : perms) {
  64. state_t sstate;
  65. for (int i = 0; i < K; ++i)
  66. for (int j = 0; j < K; ++j)
  67. sstate[i][j] = state[perm[i]][perm[j]];
  68.  
  69. auto it = cache.find(StateKey(sstate));
  70. if (it != cache.end()) {
  71. *entry = it->second;
  72. entry->i = perm[entry->i];
  73. entry->j = entry->j >= 0 ? perm[entry->j] : -1;
  74. return true;
  75. }
  76. }
  77. return false;
  78. }
  79.  
  80. // Recursively finds the best strategy.
  81. double FindBestStrategy(state_t state, int total) {
  82. entry cached;
  83. if (FindInCache(state, &cached)) return cached.prob;
  84.  
  85. // Compute probabilies of each permutation in current state.
  86. array<double, KX> probs;
  87. {
  88. double lhtotal = 0.0;
  89. for (int k = 0; k < KX; ++k) {
  90. const auto& perm = perms[k];
  91. int badcount = 0;
  92. for (int i = 0; i < K; ++i)
  93. for (int j = 0; j < K; ++j)
  94. if (perm[i] < perm[j]) badcount += state[i][j];
  95. double lhood = 1.0 / (1 << badcount);
  96. lhtotal += lhood;
  97. probs[k] = lhood;
  98. }
  99. for (double& prob : probs) prob /= lhtotal;
  100. }
  101.  
  102. entry best = {0, -1, -1};
  103. if (total == N) {
  104. // All N matches have been played. Compute the probability that
  105. // the most probable winner is the actual best player.
  106. for (int i = 0; i < K; ++i) {
  107. double prob = 0;
  108. for (int k = 0; k < KX; ++k) {
  109. const auto& perm = perms[k];
  110. if (perm[i] == K-1) prob += probs[k];
  111. }
  112. if (prob > best.prob) best = {prob, i, -1};
  113. }
  114. } else {
  115. // Consider all possible pairs of players (i, j) and recurse.
  116. for (int i = 0; i < K; ++i)
  117. for (int j = i+1; j < K; ++j) {
  118. double probi = 0.0;
  119. for (int k = 0; k < KX; ++k) {
  120. const auto& perm = perms[k];
  121. if (perm[i] > perm[j]) probi += probs[k];
  122. }
  123. double probj = 1.0 - probi;
  124. ++state[i][j]; // i won starts
  125. double successi = FindBestStrategy(state, total + 1);
  126. --state[i][j]; // i won ends
  127. ++state[j][i]; // j won starts
  128. double successj = FindBestStrategy(state, total + 1);
  129. --state[j][i]; // j won ends
  130. double prob = (probi * 2 + probj) / 3.0 * successi + (probj * 2 + probi)/3.0 * successj;
  131. if (prob > best.prob) best = {prob, i, j};
  132. }
  133. }
  134. cache.emplace(StateKey(state), best);
  135. return best.prob;
  136. }
  137.  
  138. // Display the strategy computed by FindBestStrategy in a human-readable form.
  139. void HumanStrategy(state_t state, int total, unsigned bitmask) {
  140. for (int i = 0; i < total; ++i) {
  141. cout << ((bitmask & (1<<i)) ? '|' : ' ') << " ";
  142. }
  143. cout << "|- ";
  144.  
  145. entry best;
  146. FindInCache(state, &best);
  147. if (best.j < 0) {
  148. cout << "guess " << best.i << endl;
  149. return;
  150. }
  151. cout << best.i << '>' << best.j << " -|"<< endl;
  152.  
  153. ++state[best.i][best.j];
  154. HumanStrategy(state, total+1, bitmask | (1 << total));
  155. --state[best.i][best.j];
  156.  
  157. for (int i = 0; i < total; ++i) {
  158. cout << ((bitmask & (1<<i)) ? '|' : ' ')<< " ";
  159. }
  160. cout << "|- ";
  161. cout << best.i << '<' << best.j << " -|" << endl;
  162.  
  163. ++state[best.j][best.i];
  164. HumanStrategy(state, total+1, bitmask);
  165. --state[best.j][best.i];
  166. }
  167.  
  168. // Display the strategy computed by FindBestStrategy in a machine-readable form.
  169. void MachineStrategy(state_t state) {
  170. entry best;
  171. FindInCache(state, &best);
  172. if (best.j < 0) {
  173. cout << best.i;
  174. return;
  175. }
  176.  
  177. cout << '(' << best.i << ", " << best.j << ", ";
  178.  
  179. ++state[best.i][best.j];
  180. MachineStrategy(state);
  181. --state[best.i][best.j];
  182.  
  183. cout << ", ";
  184.  
  185. ++state[best.j][best.i];
  186. MachineStrategy(state);
  187. --state[best.j][best.i];
  188.  
  189. cout << ")";
  190. }
  191.  
  192. // Compute the success probability of the strategy computed by
  193. // FindBestStrategy. Even though FindBestStrategy also computes this
  194. // number, this function serves as an independent verification, and
  195. // also is parameterized by the result type, so it can compute using
  196. // floating point or using exact rational value.
  197. template<class T>
  198. T Evaluate(state_t state, const array<int, K>& perm) {
  199. entry best;
  200. FindInCache(state, &best);
  201. if (best.j < 0) return perm[best.i] == K-1 ? T(1) : T(0);
  202.  
  203. ++state[best.i][best.j];
  204. T pij = Evaluate<T>(state, perm);
  205. --state[best.i][best.j];
  206.  
  207. ++state[best.j][best.i];
  208. T pji = Evaluate<T>(state, perm);
  209. --state[best.j][best.i];
  210.  
  211. if (perm[best.i] > perm[best.j]) return (pij * 2 + pji) / 3;
  212. else return (pij + pji * 2) / 3;
  213. }
  214.  
  215. template<class T>
  216. void Evaluate() {
  217. array<array<int, K>, K> state {0};
  218. T sum = 0.0;
  219. for (const auto& perm : perms)
  220. sum += Evaluate<T>(state, perm);
  221. T success = sum / T(perms.size());
  222. cout << "success probability: " << success << endl;
  223. }
  224.  
  225. int main() {
  226. cout.precision(17);
  227. // Compute the list of all permutations.
  228. array<int, K> perm;
  229. iota(perm.begin(), perm.end(), 0);
  230. do {
  231. perms.push_back(perm);
  232. } while (next_permutation(perm.begin(), perm.end()));
  233.  
  234. // Do the search and verification.
  235. double prob = FindBestStrategy({0}, 0);
  236. HumanStrategy({0}, 0, 0);
  237. MachineStrategy({0}); cout << endl;
  238. cout << "analysis: " << prob << endl;
  239. Evaluate<double>();
  240. // Uncomment the next line, and the #include on top if you have Boost libraries installed.
  241. // Evaluate<boost::rational<long long>>();
  242. cout << "cached items: " << cache.size() << endl;
  243. return 0;
  244. }
  245.  
  246.  
  247. // Speed optimization log
  248. //
  249. // K, N, success probability, cache items, time
  250. //
  251. // K=5, N=8, 3085/6561, 3108105, 0m40.447s # original algorithm
  252. // K=5, N=8, 3085/6561, 1282194, 0m14.357s # with 'used' symmetry breaking
  253. // K=5, N=8, 3085/6561, 1282194, 0m10.740s # with -O3 flag, with -fprofile-use is slower
  254. // K=5, N=8, 3085/6561, 27578, 0m03.257s # with permuted cache lookup
  255. // K=5, N=8, 3085/6561, 27578, 0m03.364s # removed 'used' symmetry breaking
  256. // # (superceded by permuted cache lookup)
  257. // K=5, N=8, 3085/6561, 27578, 0m00.678s # cache key is an int, not an array
  258. //
  259. // K=5, N=10, 447236/885735, 257466, 0m5.653s # same as above for N=10
  260. //
  261. // TODO: speed optimization ideas:
  262. // - cut the search early if we're already doing badly
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement