結果

問題 No.187 中華風 (Hard)
ユーザー nonpro3nonpro3
提出日時 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
権限があれば一括ダウンロードができます

ソースコード

diff #

#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;
}
0