結果
問題 |
No.2119 一般化百五減算
|
ユーザー |
![]() |
提出日時 | 2025-05-14 13:12:00 |
言語 | C++17 (gcc 13.3.0 + boost 1.87.0) |
結果 |
AC
|
実行時間 | 22 ms / 2,000 ms |
コード長 | 11,256 bytes |
コンパイル時間 | 652 ms |
コンパイル使用メモリ | 93,332 KB |
実行使用メモリ | 6,272 KB |
最終ジャッジ日時 | 2025-05-14 13:13:31 |
合計ジャッジ時間 | 1,671 ms |
ジャッジサーバーID (参考情報) |
judge5 / judge1 |
(要ログイン)
ファイルパターン | 結果 |
---|---|
other | AC * 25 |
ソースコード
#include <iostream> #include <vector> #include <numeric> // Provides std::gcd in C++17, but we implement our own for clarity/compatibility #include <limits> // For std::numeric_limits #include <cmath> // For std::abs // Use long long for potentially large numbers involved in CRT calculations typedef long long ll; // Function to compute the greatest common divisor (GCD) using the Euclidean algorithm // Ensures non-negative inputs are used. ll custom_gcd(ll a, ll b) { a = std::abs(a); b = std::abs(b); while (b) { a %= b; std::swap(a, b); } return a; } // Structure to hold the result of the Extended Euclidean Algorithm struct ExtGCDResult { ll g; // gcd(a, b) ll x; // coefficient of a ll y; // coefficient of b // Satisfies ax + by = g }; // Extended Euclidean Algorithm implementation // Returns {g, x, y} such that ax + by = g = gcd(a, b) // Assumes a, b are non-negative, which is true for the context it's used in this problem. ExtGCDResult extgcd(ll a, ll b) { if (b == 0) { // Base case: gcd(a, 0) = a. Then ax + 0y = a. Choose x=1, y=0. return {a, 1, 0}; } // Recursive step: Compute extgcd for (b, a % b) ExtGCDResult res = extgcd(b, a % b); // From result {g, x', y'} for bx' + (a % b)y' = g // We derive x, y for ax + by = g // Substitute a % b = a - floor(a/b) * b // g = bx' + (a - (a / b) * b)y' // g = bx' + ay' - (a / b) * b * y' // g = ay' + b(x' - (a / b) * y') // So x = y' and y = x' - (a / b) * y' ll x = res.y; ll y = res.x - (a / b) * res.y; return {res.g, x, y}; } // Function to merge two congruences: // n = R (mod L) // n = C (mod B) // Finds the smallest non-negative solution n satisfying both, subject to n <= N. // Returns a pair {new_R, new_L}. // new_L = -1 indicates no solution exists. // new_L = -2 indicates a candidate solution was found because the LCM exceeds N, // and this candidate must be verified against remaining congruences. // Otherwise, {new_R, new_L} represents the combined congruence n = new_R (mod new_L). std::pair<ll, ll> merge_congruences(ll R, ll L, ll C, ll B, ll N) { // Normalize C to be non-negative: C becomes C mod B in range [0, B-1] C = (C % B + B) % B; // Calculate g = gcd(L, B) ll g = custom_gcd(L, B); // Check compatibility condition: R must be congruent to C modulo g // Need to ensure R % g and C % g results are handled correctly if negative, using (val % g + g) % g if ((R % g + g) % g != (C % g + g) % g) { // Congruences are contradictory return {-1, -1}; // No solution } // From the first congruence, n = R + k*L for some integer k. // Substitute into the second congruence: R + k*L = C (mod B) // k*L = C - R (mod B) // This linear congruence has solutions for k because C - R is divisible by g = gcd(L, B). // Divide by g: k * (L/g) = (C - R)/g (mod B/g) ll L_div_g = L / g; ll B_div_g = B / g; ll C_minus_R = C - R; // C-R is guaranteed to be divisible by g from the compatibility check ll C_minus_R_div_g = C_minus_R / g; // Find modular inverse of (L/g) modulo (B/g) using Extended Euclidean Algorithm // Since gcd(L/g, B/g) = gcd(L, B)/g = g/g = 1, the inverse exists. ExtGCDResult res_inv = extgcd(L_div_g, B_div_g); // res_inv.g should be 1. If not, something is wrong. if (res_inv.g != 1) { // This indicates an issue, potentially large number related or logic error return {-1, -1}; } // The inverse is res_inv.x. Normalize it to be in [0, B/g - 1] ll inv = (res_inv.x % B_div_g + B_div_g) % B_div_g; // Solve for k: k = (C - R)/g * inv (mod B/g) // Calculate k0, the smallest non-negative solution for k. // Need careful modular multiplication, especially if C_minus_R_div_g is negative. ll k0_term1 = C_minus_R_div_g; ll k0_term2 = inv; // Compute (k0_term1 * k0_term2) % B_div_g ensuring non-negative result. ll k0_term1_mod = (k0_term1 % B_div_g + B_div_g) % B_div_g; ll k0_term2_mod = k0_term2; // inv is already non-negative and < B_div_g // Check for potential overflow before multiplication ll k0_prod; bool k0_prod_overflow = false; const ll LLONG_MAX_VAL = std::numeric_limits<long long>::max(); if (k0_term1_mod > 0 && k0_term2_mod > 0 && k0_term1_mod > LLONG_MAX_VAL / k0_term2_mod) { k0_prod_overflow = true; } else { k0_prod = k0_term1_mod * k0_term2_mod; } if (k0_prod_overflow) { // Should not happen with given constraints. If it does, implies intermediate values are too large. return {-1, -1}; } ll k0 = k0_prod % B_div_g; // Smallest non-negative k solution modulo B/g // The combined congruence is n = R' (mod L'), where L' = lcm(L, B) // Calculate the new modulus L' = lcm(L, B) = (L / g) * B ll new_L; bool L_overflow = false; // Check for overflow before calculating L' = (L/g) * B if (B == 0) L_overflow = true; // B must be positive based on constraints // Check if L/g > LLONG_MAX / B else if (L_div_g > 0 && B > 0 && L_div_g > LLONG_MAX_VAL / B) { L_overflow = true; } else { new_L = L_div_g * B; } // Calculate the smallest non-negative solution R' for the combined system. // R' = R + k0 * L ll k0L_prod; bool k0L_overflow = false; // Check for overflow before calculating k0 * L if (L == 0) k0L_prod = 0; // L cannot be 0 after first step else if (k0 > 0 && L > 0 && k0 > LLONG_MAX_VAL / L) { k0L_overflow = true; } else { k0L_prod = k0 * L; } if (k0L_overflow) { // Product k0*L overflows long long, meaning the result R' is huge. // Since N is relatively small (<= 10^5), R' > N. return {-1, -1}; // No solution <= N } // Calculate R' = R + k0*L checking for overflow during addition ll new_R; bool R_add_overflow = false; if (k0L_prod > LLONG_MAX_VAL - R) { // Check R + k0L_prod > LLONG_MAX R_add_overflow = true; } else { new_R = R + k0L_prod; } if (R_add_overflow) { // Final R' overflows long long. Result must be > N. return {-1, -1}; // No solution <= N } // Now we have the smallest non-negative solution `new_R` and the new modulus `new_L`. // Check if the new modulus `new_L` exceeds N or overflowed. if (L_overflow || new_L > N) { // If L' > N, then `new_R` is the only possible solution candidate <= N. // We must check if `new_R` itself is <= N. if (new_R > N) { // The smallest non-negative solution is already > N. return {-1, -1}; // No solution <= N } else { // `new_R` is a valid candidate <= N. Return it with flag -2 to enter candidate mode. return {new_R, -2}; } } else { // new_L is within limits and <= N. The combined congruence is n = new_R (mod new_L). // The smallest non-negative solution is new_R. It should satisfy new_R < new_L. // Since new_L <= N, it follows that new_R <= N. // However, let's double-check if new_R could exceed N. Example: N=100, L=60, B=70. lcm(60, 70)=420. If new_R is computed and L'=420, the check L' > N triggers. // If L=30, B=20. lcm(30, 20)=60. If N=50. L'=60 > N. Check triggers. // What if L=10, B=12. lcm(10, 12)=60. N=100. L'=60 <= N. Suppose R=1, C=5. g=2. R=1, C=5. 1%2=1, 5%2=1. Compatible. L/g=5, B/g=6. C-R/g = (5-1)/2=2. extgcd(5, 6) -> 5x+6y=1. x=-1, y=1. inv = -1 mod 6 = 5. k0 = (2 * 5) % 6 = 10 % 6 = 4. R'=R+k0*L = 1 + 4*10 = 41. new_L=60. N=100. new_L<=N. return {41, 60}. Okay. // Final check: if minimal solution new_R > N. This might happen if intermediate N wasn't considered properly. if (new_R > N) { return {-1, -1}; } // Return the updated state {R, L} return {new_R, new_L}; } } int main() { // Faster I/O std::ios_base::sync_with_stdio(false); std::cin.tie(NULL); ll N; // Upper bound for the solution (non-negative integer) int M; // Number of congruences (positive integer) std::cin >> N >> M; // M is guaranteed to be at least 1. ll R; // Current remainder for the combined congruence ll L; // Current modulus for the combined congruence ll B, C; // Modulus and remainder for the next congruence to merge // Read the first congruence to initialize R and L std::cin >> B >> C; if (B <= 0) { // Modulus must be positive std::cout << "NaN" << std::endl; return 0; } // Initialize R = C mod B, ensuring R is non-negative [0, B-1] R = (C % B + B) % B; L = B; // Initial modulus is B0 // Check if the smallest non-negative solution from the first congruence already exceeds N if (R > N) { std::cout << "NaN" << std::endl; return 0; } // Flag and value for candidate mode (when LCM exceeds N) bool candidate_mode = false; ll candidate_val = -1; // Process remaining M-1 congruences for (int m = 1; m < M; ++m) { std::cin >> B >> C; if (B <= 0) { // Modulus must be positive std::cout << "NaN" << std::endl; return 0; } if (candidate_mode) { // If already in candidate mode, just verify the candidate against the current congruence. ll C_norm = (C % B + B) % B; // Normalize C // Check if candidate_val satisfies n = C (mod B) // Check (candidate_val % B + B) % B == C_norm carefully if ((candidate_val % B + B) % B != C_norm) { // Candidate failed verification std::cout << "NaN" << std::endl; return 0; } // If verification passes, continue to the next congruence. Candidate remains the same. } else { // Not in candidate mode. Try to merge the current congruence (R, L) with (C, B). std::pair<ll, ll> result = merge_congruences(R, L, C, B, N); if (result.second == -1) { // merge_congruences reported no solution std::cout << "NaN" << std::endl; return 0; } else if (result.second == -2) { // merge_congruences found a candidate and activated candidate mode candidate_mode = true; candidate_val = result.first; } else { // Normal update: merge successful, L remains <= N. Update R and L. R = result.first; L = result.second; // R = result.first should already be <= N based on checks in merge_congruences } } } // After processing all congruences: if (candidate_mode) { // If we ended in candidate mode, the verified candidate is the answer. std::cout << candidate_val << std::endl; } else { // If we never entered candidate mode, the final R is the answer. // R is guaranteed to be the smallest non-negative solution and <= N. std::cout << R << std::endl; } return 0; }