解法1. 全探索
解法は Editorial を参考にしている.
番目と 番目の分子について考える. の右隣に があるとき,1種類目と2種類目の原子のある原子量 が存在して, となる. は正整数なので式変形を行うと,
となる.このとき, の範囲は の値によって次の3つの場合に分けられる.
1. のとき,
2. のとき,
3. のとき,
すべての分子の並べ方に対して,その並べ方が条件を満たすかどうかを が存在する範囲が空でないかどうかで判定する.ただし, かつ となる要素対が存在する場合は条件を満たさない. を の存在する区間の初期値として,上の条件 2, 3 によって範囲を更新する.区間の端点を double
型で計算すると精度が足りないかもしれないので有理数型で計算する(分子と分母からなる整数型の対).
計算時間:
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
template<typename T>
struct Rational {
using Int = T;
Int p, q;
Rational(Int p, Int q = 1) : p(p), q(q) { normalize(); }
Int gcd(Int a, Int b) { return b ? gcd(b, a % b) : a; }
void normalize() {
if (q < 0) p *= -1, q *= -1;
const Int d = gcd(p < 0 ? -p : p, q);
if (d == 0) p = 0, q = 1;
else p /= d, q /= d;
}
bool operator<(const Rational &rhs) const {
return this->p * rhs.q < this->q * rhs.p;
}
bool operator<=(const Rational &rhs) const {
return !(rhs < *this);
}
bool operator>(const Rational &rhs) const {
return rhs < *this;
}
bool operator>=(const Rational &rhs) const {
return !(*this < rhs);
}
bool operator==(const Rational &rhs) const {
return !(*this < rhs) && !(rhs < *this);
}
bool operator!=(const Rational &rhs) const {
return (*this < rhs) || (rhs < *this);
}
};
class Solver {
public:
Solver(int _n) : num_case(_n) {}
void input() {
cin >> N;
C.resize(N); J.resize(N);
for (int i = 0; i < N; ++i) cin >> C[i] >> J[i];
}
void print() {
cout << "Case #" << num_case << ": " << num_order << '\n';
}
void solve();
private:
const int num_case;
int N;
vector<ll> C, J;
const ll INF = 2 * 1e9;
int num_order = 0;
};
void Solver::solve() {
if (6 < N) return ;
vector<int> idx(N);
iota(idx.begin(), idx.end(), 0);
do {
Rational<ll> lb(0), ub(INF);
bool exit_mole = true;
for (int i = 0; i + 1 < N; ++i) {
const int a = idx[i], b = idx[i + 1];
if (C[a] == C[b]) {
if (J[a] > J[b]) {
exit_mole = false;
break;
}
else continue;
}
Rational<ll> k(J[a] - J[b], C[b] - C[a]);
if (C[a] < C[b] && lb < k) lb = k;
else if (C[a] > C[b] && k < ub) ub = k;
if (ub <= lb) {
exit_mole = false;
break;
}
}
if (exit_mole) ++num_order;
} while (next_permutation(idx.begin(), idx.end()));
}
int main() {
cin.tie(0); ios::sync_with_stdio(false);
int T; cin >> T;
for (int i_case = 1; i_case <= T; ++i_case) {
Solver s(i_case);
s.input();
s.solve();
s.print();
}
return 0;
}
解法2.
解法1の並べ方を全探索する部分を高速化する.
とする. の符号によって, の範囲と, 番目と 番目の分子の位置関係が変化する.
・ の場合
□ のとき,
□ のとき,
・ の場合
□ のとき,
□ のとき,
のときは, ならば , ならば となり,2分子間の位置関係は固定される.条件を満たす並べ方は分子量に関して狭義単調増加しており推移性を満たすので,上の条件は隣り合う2分子間のみだけではなく,2分子間の相対的な位置関係も決定する.すなわち, の符号は入力時に固定されるので, 番目と 番目の分子の相対的な位置関係によって が の左右どちらにあるかが決定される.
とする. の要素を昇順に一列に並べたものを とする.このとき,区間 () の全体と,条件を満たす並べ方に対応する の存在する最小区間全体との間に一対一対応が存在する.したがって,答えは, となる.
計算時間:
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
template<typename T>
struct Rational {
using Int = T;
Int p, q;
Rational(Int p, Int q = 1) : p(p), q(q) { normalize(); }
Int gcd(Int a, Int b) { return b ? gcd(b, a % b) : a; }
void normalize() {
if (q < 0) p *= -1, q *= -1;
const Int d = gcd(p < 0 ? -p : p, q);
if (d == 0) p = 0, q = 1;
else p /= d, q /= d;
}
bool operator<(const Rational &rhs) const {
return this->p * rhs.q < this->q * rhs.p;
}
bool operator<=(const Rational &rhs) const {
return !(rhs < *this);
}
bool operator>(const Rational &rhs) const {
return rhs < *this;
}
bool operator>=(const Rational &rhs) const {
return !(*this < rhs);
}
bool operator==(const Rational &rhs) const {
return !(*this < rhs) && !(rhs < *this);
}
bool operator!=(const Rational &rhs) const {
return (*this < rhs) || (rhs < *this);
}
};
class Solver {
public:
Solver(int _n) : num_case(_n) {}
void input() {
cin >> N;
C.resize(N);
J.resize(N);
for (int i = 0; i < N; ++i) {
cin >> C[i] >> J[i];
}
}
void print() {
cout << "Case #" << num_case << ": " << num_order << '\n';
}
void solve();
private:
const int num_case;
int num_order = 0;
int N;
vector<ll> C, J;
};
void Solver::solve() {
set<Rational<ll>> num_variety;
for (int i = 0; i < N; ++i) {
for (int j = i + 1; j < N; ++j) {
if ((C[i] <= C[j] && J[i] <= J[j]) ||
(C[j] <= C[i] && J[j] <= J[i])) continue;
if (C[i] != C[j]) {
Rational<ll> r(J[i] - J[j], C[j] - C[i]);
num_variety.insert(r);
}
}
}
num_order = num_variety.size() + 1;
}
int main() {
cin.tie(0); ios::sync_with_stdio(false);
int T; cin >> T;
for (int i_case = 1; i_case <= T; ++i_case) {
Solver s(i_case);
s.input();
s.solve();
s.print();
}
return 0;
}