Problem p03615 (C++)

Buggy submission (status: Runtime Error):
#include <complex>
#include <iostream>
#include <set>
#include <vector>
#define rep(i, n) for (i = 0; i < n; i++)
#define repn(i, m, n) for (i = m; i < n; i++)
#define int long long
using namespace std;
typedef complex<double> P;

int mod = 998244353;
int pow2[201];
int n;
P p[100];

double cross(P a, P b) { return a.real() * b.imag() - a.imag() * b.real(); }
bool onLine(P a, P b, P p) { return cross(b - a, p - a) == 0; }

signed main() {
  int i, j, k;

  pow2[0] = 1;
  repn(i, 1, 201) pow2[i] = (pow2[i - 1] * 2) % mod;

  cin >> n;
  rep(i, n) {
    double x, y;
    cin >> x >> y;
    p[i] = P(x, y);
  }

  int ans = pow2[n];

  set<vector<int>> dict;
  rep(i, n) {
    repn(j, i + 1, n) {
      vector<int> vec;
      rep(k, n) if (onLine(p[i], p[j], p[k])) vec.push_back(k);
      if (dict.find(vec) != dict.end())
        continue;
      dict.insert(vec);
      ans += mod - (pow2[vec.size()] - vec.size() - 1);
      ans %= mod;
    }
  }
  ans += mod - n - 1;
  ans %= mod;
  cout << ans << endl;
  return 0;
}

// 点集合Sについて、Sが凸多角形なら「2^(凸多角形Sの内部にある点の個数)」がスコアになる。それ以外なら0がスコアになる。スコアの和を求めたい。
// 2^nが何を表すか？例えば、{1,2,…,n}の部分集合は2^n個ある。つまり、Sが凸多角形のときのスコアは、{Sの内部にある点}の部分集合の個数と等しい。
// そこで、「点集合Sについて、Sの凸法が存在するなら1点,
// 存在しないなら0点」というスコアリングを考える。実はこれの和が、そのまま答えになる。
// なぜだろうか。Sの凸法が存在するとき、凸法の点集合をTとおいてみる。Sが与えられたとき、Tはちょうど1個に定まる。ここで、S->Tという写像fを
// 考える。すると、各Tについて「2^(凸多角形Tの内部にある点の個数)」だけ矢印が来ていることが分かる。
// また、写像fによって得られるTの集合（値域）は、与えられたN個の点によって作ることのできる「凸多角形」の集合と一致する。
// 新しいスコアリングでは、矢印の個数の和が合計スコアとなるが、これは上述より本問の答えと一致した。
//
// したがって、「凸法が存在するような点集合S」の個数を求める問題になる。補問題として「凸法が存在しないような点集合S」を考えると、Sは
// 直線または点または空集合を表すことが分かる。したがって、まず「答え=2^N」を仮定し、
// そのような直線を全部列挙し、2^(乗っている点の個数) - (乗っている点の個数) - 1
// を答えから引いていき、最後に答えからN+1を引けば、正しい答えが求まる。
// 計算量はO(N^3)となる()
//
// 直線の列挙法：
// 直線は「乗っている点の集合X」と一対一対応するので、Xが今までに出現していないときだけ、リストに追加すればOK.
// 既に現れた点集合Xを、ハッシュで管理するとO(N^3),
// setで管理するとO(N^3logN)となる。

Fixed submission:
#include <complex>
#include <iostream>
#include <set>
#include <vector>
#define rep(i, n) for (i = 0; i < n; i++)
#define repn(i, m, n) for (i = m; i < n; i++)
#define int long long
using namespace std;
typedef complex<double> P;

int mod = 998244353;
int pow2[201];
int n;
P p[200];

double cross(P a, P b) { return a.real() * b.imag() - a.imag() * b.real(); }
bool onLine(P a, P b, P p) { return cross(b - a, p - a) == 0; }

signed main() {
  int i, j, k;

  pow2[0] = 1;
  repn(i, 1, 201) pow2[i] = (pow2[i - 1] * 2) % mod;

  cin >> n;
  rep(i, n) {
    double x, y;
    cin >> x >> y;
    p[i] = P(x, y);
  }

  int ans = pow2[n];

  set<vector<int>> dict;
  rep(i, n) {
    repn(j, i + 1, n) {
      vector<int> vec;
      rep(k, n) if (onLine(p[i], p[j], p[k])) vec.push_back(k);
      if (dict.find(vec) != dict.end())
        continue;
      dict.insert(vec);
      ans += mod - (pow2[vec.size()] - vec.size() - 1);
      ans %= mod;
    }
  }
  ans += mod - n - 1;
  ans %= mod;
  cout << ans << endl;
  return 0;
}

// 点集合Sについて、Sが凸多角形なら「2^(凸多角形Sの内部にある点の個数)」がスコアになる。それ以外なら0がスコアになる。スコアの和を求めたい。
// 2^nが何を表すか？例えば、{1,2,…,n}の部分集合は2^n個ある。つまり、Sが凸多角形のときのスコアは、{Sの内部にある点}の部分集合の個数と等しい。
// そこで、「点集合Sについて、Sの凸法が存在するなら1点,
// 存在しないなら0点」というスコアリングを考える。実はこれの和が、そのまま答えになる。
// なぜだろうか。Sの凸法が存在するとき、凸法の点集合をTとおいてみる。Sが与えられたとき、Tはちょうど1個に定まる。ここで、S->Tという写像fを
// 考える。すると、各Tについて「2^(凸多角形Tの内部にある点の個数)」だけ矢印が来ていることが分かる。
// また、写像fによって得られるTの集合（値域）は、与えられたN個の点によって作ることのできる「凸多角形」の集合と一致する。
// 新しいスコアリングでは、矢印の個数の和が合計スコアとなるが、これは上述より本問の答えと一致した。
//
// したがって、「凸法が存在するような点集合S」の個数を求める問題になる。補問題として「凸法が存在しないような点集合S」を考えると、Sは
// 直線または点または空集合を表すことが分かる。したがって、まず「答え=2^N」を仮定し、
// そのような直線を全部列挙し、2^(乗っている点の個数) - (乗っている点の個数) - 1
// を答えから引いていき、最後に答えからN+1を引けば、正しい答えが求まる。
// 計算量はO(N^3)となる()
//
// 直線の列挙法：
// 直線は「乗っている点の集合X」と一対一対応するので、Xが今までに出現していないときだけ、リストに追加すればOK.
// 既に現れた点集合Xを、ハッシュで管理するとO(N^3),
// setで管理するとO(N^3logN)となる。