SRM 702 Div 2

1年間隔ぐらいで思い出したように参加している。

結果

oo-

Easy - TestTaking

class TestTaking {
public:
  int findMax(int _questions, int _guessed, int _actual) {
    return _questions - abs(_actual - _guessed);
  }
};

Medium - GridSort

ソート可能 \(\iff\) 全ての列で各要素を\(m\)で割った余りが同じ、かつ全ての行内で(各要素 - 1) / \(m\)が同じ

class GridSort {
public:
  string sort(int n, int m, vector<int> grid) {
    for(int i = 0; i < n; i++) {
      int div = (grid[i*m] - 1) / m;
      for(int j = 0; j < m; j++) {
        if((grid[i*m + j] - 1) / m != div) return "Impossible";
      }
    }
    for(int j = 0; j < m; j++) {
      int rem = grid[j] % m;
      for(int i = 0; i < n; i++) {
        if(grid[i*m + j] % m != rem) return "Impossible";
      }
    }
    return "Possible";
  }
};

Hard - SubsetSubExtreme

方針

各試行が独立で、プレーヤーがスコアの期待値を最小にするように振る舞うことを考えると、
ブロックの集合が\(B\)の時の期待値を\(E(B)\)とすると、
$$ E(B) = \frac{1}{N_{face}} \sum_{i=1}^{N_{face}} min(\lbrace sum(B) \rbrace \cup \lbrace E(B-S) \mid S \in \mathfrak{P}(B), sum(S) = face[i] \rbrace) $$

ただし\(\mathfrak{P}(B)\)は\(B\)のべき集合。

集合は\(i\)番目のbitが1なら含まれていて、0なら含まれていないという形でintにエンコードする。

時間計算量

ひとつのBに対しては、サイコロの面で\(N_{face}\)、掛ける、べき集合を回すので\(2^{N_{block}}\)。それをメモ化すれば与えられたブロックの全部分集合に対して1度ずつ計算することになるので、\( O(N_{face} \times 2^{N_{block}} \times 2^{N_{block}}) \)で、最大で10^8ぐらい。

コード

class SubsetSumExtreme {
public:
  vector<int> block;
  vector<int> face;
  vector<double> memo;
  vector<double> subsetSum;

  double getExpectation(vector<int> _block, vector<int> _face) {
    block = _block, face = _face;
    memo = vector<double>(1 << block.size(), -1.0);
    subsetSum = vector<double>(1 << block.size(), 0.0);
    for(int s = 0; s < (1 << block.size()); s++) {
      for(int j = 0; j < block.size(); j++) {
        if(s & (1 << j)) subsetSum[s] += block[j];
      }
    }
    return exp((1 << block.size()) - 1);
  }

  double exp(int blocks) {
    if(memo[blocks] != -1.0) return memo[blocks];
    double res = 0.0;
    for(int i = 0; i < face.size(); i++) {
      double subres = 0.0;
      for(int j = 0; j < block.size(); j++) if(blocks & (1 << j)) subres += block[j];
      for(int s = 0; s < (1 << block.size()); s++) {
        if(((blocks & s) == s) &&  subsetSum[s] == face[i]) subres = min(subres, exp(blocks &~ s));
      }
      res += subres;
    }
    res /= face.size();
    memo[blocks] = res;
    return res;
  }
};

感想

Hardは方針まではほとんどたっていたんだけど、細かい部分を詰めるところ、集合周りの実装で手間取って解ききることができなかった。悔しい。

コンテストに参加はしていなかったけど、MIT OCWのIntroduction to Algoriththmsの講義ビデオを眺めたりはしていて、少しは地力があがっているかもしれない。