結果
問題 | No.187 中華風 (Hard) |
ユーザー | nonpro3 |
提出日時 | 2021-03-04 14:51:57 |
言語 | C++14 (gcc 12.3.0 + boost 1.83.0) |
結果 |
AC
|
実行時間 | 344 ms / 3,000 ms |
コード長 | 6,377 bytes |
コンパイル時間 | 901 ms |
コンパイル使用メモリ | 76,460 KB |
実行使用メモリ | 5,248 KB |
最終ジャッジ日時 | 2024-10-04 21:41:57 |
合計ジャッジ時間 | 6,006 ms |
ジャッジサーバーID (参考情報) |
judge5 / judge3 |
(要ログイン)
テストケース
テストケース表示入力 | 結果 | 実行時間 実行使用メモリ |
---|---|---|
testcase_00 | AC | 2 ms
5,248 KB |
testcase_01 | AC | 2 ms
5,248 KB |
testcase_02 | AC | 246 ms
5,248 KB |
testcase_03 | AC | 241 ms
5,248 KB |
testcase_04 | AC | 334 ms
5,248 KB |
testcase_05 | AC | 334 ms
5,248 KB |
testcase_06 | AC | 334 ms
5,248 KB |
testcase_07 | AC | 333 ms
5,248 KB |
testcase_08 | AC | 221 ms
5,248 KB |
testcase_09 | AC | 223 ms
5,248 KB |
testcase_10 | AC | 219 ms
5,248 KB |
testcase_11 | AC | 337 ms
5,248 KB |
testcase_12 | AC | 344 ms
5,248 KB |
testcase_13 | AC | 77 ms
5,248 KB |
testcase_14 | AC | 71 ms
5,248 KB |
testcase_15 | AC | 240 ms
5,248 KB |
testcase_16 | AC | 244 ms
5,248 KB |
testcase_17 | AC | 1 ms
5,248 KB |
testcase_18 | AC | 3 ms
5,248 KB |
testcase_19 | AC | 1 ms
5,248 KB |
testcase_20 | AC | 255 ms
5,248 KB |
testcase_21 | AC | 2 ms
5,248 KB |
testcase_22 | AC | 338 ms
5,248 KB |
testcase_23 | AC | 2 ms
5,248 KB |
testcase_24 | AC | 2 ms
5,248 KB |
ソースコード
#include<iostream> #include<algorithm> #include<vector> using namespace std; typedef long long ll; //中国剰余定理で存在を保証されている値の計算。 //連立合同式を解く。 namespace CRT { ll gcd(ll a, ll b) { if (a % b == 0)return b; return gcd(b, a % b); } ll lcm(ll a, ll b) { return a * b / gcd(a, b); } //e_gcd()自体はaとbの最大公約数を返す。 //xとyは、a*x+b*y=gcd(a,b)となる(x,y)の1つの組を返す。 //a*x+b*y=gcdの時、a=b*p+(a%b)を代入して、b*p*x+(a%b)*x+b*y=b*(p*x+y)+(a%b)*x //次のx->p*x+y //次のy->x //次のa->b //次のb->a%b ll e_gcd(ll a, ll b, ll& x, ll& y) { if (b == 0) { x = 1; y = 0; return a; } ll gcd_res = e_gcd(b, a % b, x, y); ll p = a / b; ll nx = x, ny = y; x = ny; y = nx - p * x; return gcd_res; } //拡張GCDを複数回使用することで得られる復元方法。 //n本の式があったとき、O(n log m) //ただし、多倍長対策はしていない。 //合同式を連立させたときの中国剰余定理で保障された解のうちの1つXを返す。 //(返すのは求めたx, lcm) //解なしの場合は、(-1, -1)と返す。 //オーバーフローする危険がある。かなり大きくなるかつ、任意modが欲しいならGarner's Algorithmを使うこと。 std::pair<ll, ll> CRT(const vector<ll>& val, const vector<ll>& mod) { //O(n log(max{mod[i]})) //x == val[i] (mod[i])がn本ある感じ if (val.size() != mod.size())return make_pair(-1, -1); int n = val.size(); ll x = 0;//最初は0 (mod 1)で始める。 ll mlcm = 1; for (int i = 0; i < n; i++) { //m[i] % mgcdが等しくない場合は、解がない。 ll p, q; ll mgcd = e_gcd(mlcm, mod[i], p, q);//pとqの符号は逆になる if ((val[i] - x) % mgcd != 0) { return make_pair(-1, -1); } //実際のpを求め(e_gcdのは右辺がgcd(mlcm, mod[i])でval[i]-xと定数倍の差がある。 //その差を補正して、mod[i] / mgcdであまりを取ると正で一番小さいpを得られる。 //pは常に正でqは常に負として考える。 ll min_p = (p * (val[i] - x) / mgcd) % (mod[i] / mgcd); if (min_p < 0)min_p += mod[i] / mgcd; x += min_p * mlcm;//これはpでの計算 //理論上、以下のようにqで求めてもいい。 //しかし、実際はオーバーフローする。 //ll max_q = (q * (val[i] - x) / mgcd) % (mlcm / mgcd); //if(max_q > 0)max_q -= mlcm / mgcd; //x = val[i] - max_q * mod[i]; mlcm = mlcm * (mod[i] / mgcd); } x %= mlcm; return make_pair(x, mlcm); } //ユークリッドの互除法から逆元を求める関数。解なしなら-1を返す。 ll invModGCD(const ll val, const ll mod) { ll p, q; if (e_gcd(val, mod, p, q) != 1) return -1; p %= mod; if (p < 0)p += mod; return p; } //Garner's Algorithmに適用させるために、mod同士が互いに素になるようにする。 //Garnerのアルゴリズムの前に実行するべき(というか、Garner()でWrapして使うことになると思う) //そもそも与えられた式が解なしなら、false、それ以外ならtrueを返す。 bool preGarner(vector<ll>& val, vector<ll>& mod) { //計算量O(n^2 * log(max{mod[i]})) int n = val.size(); for (int i = 0; i < n; i++) { for (int j = 0; j < i; j++) { ll g = gcd(mod[i], mod[j]); if ((val[j] - val[i]) % g != 0) return false; //ここの部分はブログで詳しく例を挙げて紹介している。 mod[i] /= g, mod[j] /= g; ll gi = gcd(mod[i], g); ll gj = g / gi; //ちなみにこのループは一度では終わらない。 do{ ll g = gcd(gi, gj); gi *= g, gj /= g; } while (gcd(gi, gj) != 1); mod[i] *= gi, mod[j] *= gj; //val[i], val[j]は小さくなったmod[i], mod[j]に合わせて再計算する。 val[i] %= mod[i], val[j] %= mod[j]; } } return true; } //Garner's Algorithmの本体。 //計算量はO(n^2 * (log(max{mod[i]}))) //普通の拡張ユークリッドでの復元と比べてnの次元が1つ上がってる。 ll GarnerBody(const vector<ll>& val, const vector<ll>& mod, const ll MOD) { int n = val.size(); ll x_MOD = val[0];//MODで割れれてるもの ll m_mul_MOD = 1; ll lcm_MOD = val[0]; vector<ll> t(n);//MODで割られてないもの t[0]は使わない for (int i = 1; i < n; i++) { //mod mod[i]でのt[i]を求める。t[i] ll x_mod = val[0] % mod[i]; ll m_mul_mod = 1; //現時点のx_MODの値をx_modにするためには、線形時間で再計算するしかない。 for (int j = 1; j < i; j++) { m_mul_mod *= mod[j - 1], m_mul_mod %= mod[i]; x_mod += m_mul_mod * t[j], x_mod%= mod[i]; } ll m_inv_mod = 1; for (int j = 0; j < i; j++) { m_inv_mod *= invModGCD(mod[j], mod[i]), m_inv_mod %= mod[i]; } ll val_minus_x_mod = (val[i] - x_mod) % mod[i]; if (val_minus_x_mod < 0)val_minus_x_mod += mod[i]; t[i] = val_minus_x_mod * m_inv_mod, t[i] %= mod[i]; m_mul_MOD *= mod[i - 1], m_mul_MOD %= MOD; //x_MODは累積和的に更新できる。 x_MOD += t[i] * m_mul_MOD, x_MOD %= MOD; } return x_MOD; } //Garner's Algorithmのラッパー関数 これを呼んで使えば間違いない。 //参照渡ししたvectorは変わりうる(modが互いに素じゃないとき)ので注意 //(連立合同式の解, LCM) ただし、すべて%MODされている。 //解なしの時、CRTと同様に(-1, -1)を返す。 pair<ll, ll> Garner(vector<ll>& val, vector<ll>& mod, const ll MOD) { if (val.size() != mod.size()) return make_pair(-1, -1); if (!preGarner(val, mod)) return make_pair(-1, -1); ll lcm = 1; for (int i = 0; i < mod.size(); i++) lcm *= mod[i], lcm %= MOD; return make_pair(GarnerBody(val, mod, MOD), lcm); } }; int N; vector<ll> b, mod; int main() { cin >> N; b.resize(N), mod.resize(N); bool all_zero = true; for (int i = 0; i < N; i++) { cin >> b[i] >> mod[i]; if (b[i] != 0)all_zero = false; } auto ret = CRT::Garner(b, mod, 1000000007); if (ret.first == -1) { cout << -1 << endl; return 0; } if (all_zero) { //すべてのPreGarner()後のmod[]を全部かけてMOD1e9+7する。 cout << ret.second << endl; return 0; } cout << ret.first << endl; return 0; }