結果
| 問題 |
No.97 最大の値を求めるくえり
|
| コンテスト | |
| ユーザー |
qwewe
|
| 提出日時 | 2025-05-14 13:18:37 |
| 言語 | C++17 (gcc 13.3.0 + boost 1.87.0) |
| 結果 |
AC
|
| 実行時間 | 541 ms / 5,000 ms |
| コード長 | 6,703 bytes |
| コンパイル時間 | 1,304 ms |
| コンパイル使用メモリ | 99,444 KB |
| 実行使用メモリ | 6,272 KB |
| 最終ジャッジ日時 | 2025-05-14 13:19:30 |
| 合計ジャッジ時間 | 4,093 ms |
|
ジャッジサーバーID (参考情報) |
judge2 / judge1 |
(要ログイン)
| ファイルパターン | 結果 |
|---|---|
| other | AC * 19 |
ソースコード
#include <iostream>
#include <vector>
#include <numeric>
#include <algorithm>
#include <cmath> // For std::sqrt and std::ceil
// PRNG implementation (provided in problem statement)
// State variables for the xor128 pseudo-random number generator
unsigned xor128_x = 123456789, xor128_y = 362436069, xor128_z = 521288629, xor128_w = 88675123;
// xor128 function to generate pseudo-random unsigned integers
unsigned xor128() {
unsigned t = xor128_x ^ (xor128_x << 11);
xor128_x = xor128_y; xor128_y = xor128_z; xor128_z = xor128_w;
return xor128_w = xor128_w ^ (xor128_w >> 19) ^ (t ^ (t >> 8));
}
// Define the modulus P as a constant. P = 100003 is given in the problem statement and is prime.
const int P = 100003;
// Modular exponentiation function: computes (base^exp) % P
// Uses binary exponentiation (also known as exponentiation by squaring) for efficiency.
long long power(long long base, long long exp) {
long long res = 1; // Initialize result
base %= P; // Take base modulo P
while (exp > 0) {
// If exp is odd, multiply result with base
if (exp % 2 == 1) res = (res * base) % P;
// Square the base and take modulo P
base = (base * base) % P;
// Halve the exponent (integer division)
exp /= 2;
}
return res;
}
// Modular multiplicative inverse function: computes n^(-1) mod P
// Uses Fermat's Little Theorem: n^(P-2) % P for prime P and n non-zero mod P.
long long modInverse(long long n) {
// We assume n is in [1, P-1] because the case q=0 is handled separately before calling this.
return power(n, P - 2);
}
// Use std::vector<bool> which is a space-optimized specialization for boolean values.
// It will store presence information for elements {0, ..., P-1}.
std::vector<bool> isPresent;
int main() {
// Use fast I/O operations to speed up reading input.
std::ios_base::sync_with_stdio(false);
std::cin.tie(NULL);
int N, Q; // N: length of sequence A, Q: number of queries
std::cin >> N >> Q;
std::vector<int> A(N); // Sequence A stored in a vector
// Generate sequence A using the provided PRNG function
for (int i = 0; i < N; ++i) {
A[i] = xor128() % P; // Generate random value and take modulo P
}
// Determine a threshold for switching between two strategies.
// A threshold of sqrt(P) is a good heuristic based on complexity analysis.
// Using ceil ensures the threshold is at least sqrt(P).
int threshold = static_cast<int>(std::ceil(std::sqrt(P)));
// For P=100003, sqrt(P) is approx 316.2, so threshold will be 317.
if (N <= threshold) {
// Strategy for small N: Iterate through all elements of A for each query.
// Time complexity per query: O(N). Total time: O(N + QN).
// Since N <= sqrt(P), total time is O(sqrt(P) + Q*sqrt(P)).
for (int i = 0; i < Q; ++i) {
int q; // Current query value
std::cin >> q;
// Handle the q=0 case separately: The maximum value is always 0.
if (q == 0) {
std::cout << 0 << "\n";
continue;
}
int max_val = 0; // Initialize maximum value found so far
// Problem constraints state N >= 1, so A is guaranteed to be non-empty.
if (N > 0) { // This check is technically redundant due to N>=1 constraint but safe to have.
// Initialize max_val with the result from the first element A[0]
max_val = (1LL * q * A[0]) % P; // Use 1LL to promote q to long long for multiplication
// Iterate through the rest of the elements (from index 1)
for (int j = 1; j < N; ++j) {
// Use long long for intermediate product (q * A[j]) to prevent overflow before modulo
long long current_val = (1LL * q * A[j]) % P;
// Update max_val if the current value is larger
if (current_val > max_val) {
max_val = current_val;
}
}
}
// Output the maximum value found for this query
std::cout << max_val << "\n";
}
} else {
// Strategy for large N: Use a boolean array for fast O(1) lookups of element presence.
// Preprocessing time: O(N) to generate A + O(P) to initialize vector<bool> + O(N) to populate it = O(N+P).
isPresent.assign(P, false); // Initialize boolean array of size P to all false. O(P) time.
// Mark elements present in A as true in the boolean array. O(N) time.
for (int a : A) {
isPresent[a] = true;
}
// Process each query using the precomputed presence information.
// Average query time complexity: O(log P + P/|S|), where |S| is number of distinct elements in A.
// Since N > sqrt(P), average query time is expected to be O(log P + sqrt(P)).
// Total time: O(N+P + Q*(log P + sqrt(P))).
for (int i = 0; i < Q; ++i) {
int q; // Current query value
std::cin >> q;
// Handle the q=0 case separately: The maximum value is always 0.
if (q == 0) {
std::cout << 0 << "\n";
continue;
}
// Calculate modular inverse of q needed for the alternative iteration strategy. O(log P) time.
long long qinv = modInverse(q);
// Initialize the maximum k found so far. It should default to 0 just in case.
// Since N > threshold >= 1, A is non-empty and some k value will be found.
int max_k = 0;
// Search for the largest k such that (k * qinv) % P is an element present in A.
// Iterate k downwards from P-1 to 0.
for (int k = P - 1; k >= 0; --k) {
// Calculate potential element a_k = (k * qinv) % P.
// Use 64-bit intermediate type (long long) for k * qinv to prevent overflow.
long long a_k = (static_cast<long long>(k) * qinv) % P;
// Check if the element a_k is present in A using the boolean array (O(1) lookup).
if (isPresent[static_cast<int>(a_k)]) {
max_k = k; // Found the largest k that satisfies the condition
break; // Stop searching as we found the maximum possible k
}
}
// Output the found maximum k value, which corresponds to the maximum (q*a)%P value.
std::cout << max_k << "\n";
}
}
return 0; // Indicate successful execution
}
qwewe