個から
個を選ぶ設定の問題!
問題概要
個の整数値
が与えられる (負値もありうる)。
これらの整数から 個選んで積をとった値の最大値を、1000000007 で割った余りを求めよ。
制約
考えたこと
本質的に、次の 2 パターンに分かれると考えた。
- パターン 1:
個の積をどのように選んでも負値になる場合
- パターン 2:
個の積を 0 以上にすること可能である場合
パターン 1 になるための必要十分条件をまず考えることにする。 個の整数のうち、負値の個数を
、0 以上の個数を
としたとき、
min(m, K) / 2 * 2 + p < K
が必要十分条件となる。この場合は、絶対値を小さくするのがよいので、 を絶対値の小さい順にソートして、その順に
個の積をとれば OK。
個の積を 0 以上にすること可能である場合
残るパターンを考える。まず、 個の値
を「負値」と「0 以上」とに分けることとした:
minus←のうち、負値であるものの集合
plus←のうち、0 以上であるものの集合
ここで、minus から大きい順に min(minus.size(), K) / 2 * 2 個 (最大個数) 選び、残りを plus から大きい順に選ぶという方法を基準に考えることとした。
この状態からスタートして「minus から選ぶ個数を 2 減らす」「plus から選ぶ個数を 2 増やす」という操作を繰り返して、積が最大になる地点を見出したい。
難しいのは、"1000000007 で割った余り" をとってしまうと大小比較ができないことだ。これについては、次のように考えた。
- 「
minusから選ぶ個数を 2 減らす」ときの、その 2 数の積を - 「
plusから選ぶ個数を 2 増やす」ときの、その 2 数の積を
としたときに、
であれば、
個の数の積は大きくなる
であれば、
個の数の積は小さくなる
と考えられる。ここで、一度でも という関係が成立した場合、その後の操作でもずっと
という関係が成立することに着目しよう (容易に証明できる)。
よって、 である限りは、操作を実行していけばよい。この解法の計算量は
となる。
コード
#include <bits/stdc++.h> using namespace std; // modint template<int MOD> struct Fp { // inner value long long val; // constructor constexpr Fp() : val(0) { } constexpr Fp(long long v) : val(v % MOD) { if (val < 0) val += MOD; } constexpr long long get() const { return val; } constexpr int get_mod() const { return MOD; } // arithmetic operators constexpr Fp operator + () const { return Fp(*this); } constexpr Fp operator - () const { return Fp(0) - Fp(*this); } constexpr Fp operator + (const Fp &r) const { return Fp(*this) += r; } constexpr Fp operator - (const Fp &r) const { return Fp(*this) -= r; } constexpr Fp operator * (const Fp &r) const { return Fp(*this) *= r; } constexpr Fp operator / (const Fp &r) const { return Fp(*this) /= r; } constexpr Fp& operator += (const Fp &r) { val += r.val; if (val >= MOD) val -= MOD; return *this; } constexpr Fp& operator -= (const Fp &r) { val -= r.val; if (val < 0) val += MOD; return *this; } constexpr Fp& operator *= (const Fp &r) { val = val * r.val % MOD; return *this; } constexpr Fp& operator /= (const Fp &r) { long long a = r.val, b = MOD, u = 1, v = 0; while (b) { long long t = a / b; a -= t * b, swap(a, b); u -= t * v, swap(u, v); } val = val * u % MOD; if (val < 0) val += MOD; return *this; } constexpr Fp pow(long long n) const { Fp res(1), mul(*this); while (n > 0) { if (n & 1) res *= mul; mul *= mul; n >>= 1; } return res; } constexpr Fp inv() const { Fp res(1), div(*this); return res / div; } // other operators constexpr bool operator == (const Fp &r) const { return this->val == r.val; } constexpr bool operator != (const Fp &r) const { return this->val != r.val; } constexpr Fp& operator ++ () { ++val; if (val >= MOD) val -= MOD; return *this; } constexpr Fp& operator -- () { if (val == 0) val += MOD; --val; return *this; } constexpr Fp operator ++ (int) const { Fp res = *this; ++*this; return res; } constexpr Fp operator -- (int) const { Fp res = *this; --*this; return res; } friend constexpr istream& operator >> (istream &is, Fp<MOD> &x) { is >> x.val; x.val %= MOD; if (x.val < 0) x.val += MOD; return is; } friend constexpr ostream& operator << (ostream &os, const Fp<MOD> &x) { return os << x.val; } friend constexpr Fp<MOD> pow(const Fp<MOD> &r, long long n) { return r.pow(n); } friend constexpr Fp<MOD> inv(const Fp<MOD> &r) { return r.inv(); } }; // Binomial coefficient template<class mint> struct BiCoef { vector<mint> fact_, inv_, finv_; constexpr BiCoef() {} constexpr BiCoef(int n) : fact_(n, 1), inv_(n, 1), finv_(n, 1) { init(n); } constexpr void init(int n) { fact_.assign(n, 1), inv_.assign(n, 1), finv_.assign(n, 1); int MOD = fact_[0].get_mod(); for(int i = 2; i < n; i++){ fact_[i] = fact_[i-1] * i; inv_[i] = -inv_[MOD%i] * (MOD/i); finv_[i] = finv_[i-1] * inv_[i]; } } constexpr mint com(int n, int k) const { if (n < k || n < 0 || k < 0) return 0; return fact_[n] * finv_[k] * finv_[n-k]; } constexpr mint fact(int n) const { if (n < 0) return 0; return fact_[n]; } constexpr mint inv(int n) const { if (n < 0) return 0; return inv_[n]; } constexpr mint finv(int n) const { if (n < 0) return 0; return finv_[n]; } }; const int MOD = 1000000007; using mint = Fp<MOD>; int main() { int N, K; cin >> N >> K; vector<long long> A(N), plus, minus, all; for (int i = 0; i < N; ++i) { cin >> A[i]; if (A[i] >= 0) plus.push_back(A[i]), all.push_back(A[i]); else minus.push_back(-A[i]), all.push_back(-A[i]); } sort(plus.begin(), plus.end(), greater<long long>()); sort(minus.begin(), minus.end(), greater<long long>()); sort(all.begin(), all.end()); // 0 以下であることが確定する場合:絶対値が最小になるようにする if (min((int)minus.size(), K) / 2 * 2 + plus.size() < K) { mint res = -1; for (int i = 0; i < K; ++i) res *= all[i]; cout << res << endl; return 0; } // 理論上最も多くの偶数個の「-」を取る場合を最初に求める int m = min((int)minus.size(), K) / 2 * 2; mint res = 1; for (int i = 0; i < m; ++i) res *= minus[i]; for (int j = 0; j < K - m; ++j) res *= plus[j]; // 「-」側を 2 個減らし、「+」を 2 個増やすことで、答えが増加する限り、それを繰り返す for (int i = m - 2, j = K - i; i >= 0 && j <= plus.size(); i -= 2, j += 2) { if (minus[i] * minus[i+1] > plus[j-1] * plus[j-2]) break; res /= minus[i], res /= minus[i+1], res *= plus[j-1], res *= plus[j-2]; } cout << res << endl; }