結果
| 問題 |
No.919 You Are A Project Manager
|
| コンテスト | |
| ユーザー |
qwewe
|
| 提出日時 | 2025-05-14 13:14:48 |
| 言語 | C++17 (gcc 13.3.0 + boost 1.87.0) |
| 結果 |
AC
|
| 実行時間 | 41 ms / 3,000 ms |
| コード長 | 10,422 bytes |
| コンパイル時間 | 1,593 ms |
| コンパイル使用メモリ | 115,220 KB |
| 実行使用メモリ | 7,844 KB |
| 最終ジャッジ日時 | 2025-05-14 13:15:51 |
| 合計ジャッジ時間 | 4,256 ms |
|
ジャッジサーバーID (参考情報) |
judge3 / judge5 |
(要ログイン)
| ファイルパターン | 結果 |
|---|---|
| sample | AC * 3 |
| other | AC * 55 |
ソースコード
#include <iostream>
#include <vector>
#include <algorithm>
#include <map>
#include <vector>
#include <cmath> // For std::log2
// Define a structure for the nodes of the persistent segment tree
struct Node {
long long count; // Stores the number of elements in the range represented by this node
int left, right; // Indices of the left and right children in the 'tree' vector (node pool)
Node() : count(0), left(0), right(0) {} // Default constructor
};
// Global vector to store all nodes of the segment trees (node pool)
std::vector<Node> tree;
// Global vector to store the root indices of different versions of the segment tree
// roots[i] stores the root for the prefix A[0...i-1]
std::vector<int> roots;
// Global index for allocating new nodes from the pool
int node_pool_idx = 1; // Start idx from 1, index 0 represents a null node
/**
* @brief Updates the persistent segment tree by adding an element.
*
* Creates a new version of the tree based on a previous version 'prev_root'
* by incrementing the count for the element at position 'pos' (in the compressed value range).
* The segment tree operates on the value range [L, R].
*
* @param prev_root Index of the root node of the previous version.
* @param L The lower bound of the current node's value range.
* @param R The upper bound of the current node's value range.
* @param pos The compressed rank (position) of the value being added.
* @return Index of the root node of the new version.
*/
int update(int prev_root, int L, int R, int pos) {
int new_root = node_pool_idx++; // Allocate a new node index
// Dynamic resizing of the node pool vector if it runs out of space
if (new_root >= tree.size()) {
// Increase size; using 1.5x growth factor is common for amortized efficiency
tree.resize(tree.size() * 1.5 + 1);
}
// Copy data from the corresponding node in the previous version
tree[new_root] = tree[prev_root];
tree[new_root].count++; // Increment the count because we added an element in this path
// If this is a leaf node (range contains single value), return the new node index
if (L == R) {
return new_root;
}
int mid = L + (R - L) / 2; // Calculate midpoint, avoiding potential overflow
// Recursively update the appropriate child (left or right) based on 'pos'
if (pos <= mid) { // If the position is in the left half of the range
tree[new_root].left = update(tree[prev_root].left, L, mid, pos);
} else { // If the position is in the right half
tree[new_root].right = update(tree[prev_root].right, mid + 1, R, pos);
}
return new_root; // Return index of the newly created root node for this version
}
/**
* @brief Queries the persistent segment tree to find the rank of the k-th smallest element.
*
* Finds the rank (index in the compressed value range) of the k-th smallest element
* within the subarray represented by the difference between tree versions 'r2' and 'r1'.
* The segment tree operates on the value range [L, R].
*
* @param r1 Index of the root node for the version representing the prefix *before* the query range.
* @param r2 Index of the root node for the version representing the prefix *at the end* of the query range.
* @param L The lower bound of the current node's value range.
* @param R The upper bound of the current node's value range.
* @param k The rank of the element to find (e.g., k=1 for minimum, k=N/2 for median-like).
* @return The compressed rank (index) of the k-th smallest element.
*/
int query(int r1, int r2, int L, int R, int k) {
// Base case: If we reach a leaf node, its range [L,R] has L=R. This L is the rank.
if (L == R) {
return L;
}
// Calculate the count of elements in the left subtree within the specified range.
// This is the difference between counts in version r2 and version r1.
long long left_count = tree[tree[r2].left].count - tree[tree[r1].left].count;
int mid = L + (R - L) / 2; // Calculate midpoint
// Decide whether to search in the left or right subtree
if (k <= left_count) { // If the k-th element is within the left subtree's count
return query(tree[r1].left, tree[r2].left, L, mid, k);
} else { // If the k-th element is within the right subtree's count
// Adjust k for the search in the right subtree: we are now looking for the (k - left_count)-th element in the right part.
return query(tree[r1].right, tree[r2].right, mid + 1, R, k - left_count);
}
}
int main() {
// Use faster I/O operations
std::ios_base::sync_with_stdio(false);
std::cin.tie(NULL);
int N; // Number of programmers
std::cin >> N;
std::vector<long long> A(N); // Stores motivation values
std::vector<long long> unique_vals_vec; // Used for coordinate compression
unique_vals_vec.reserve(N); // Reserve capacity to potentially avoid reallocations
for (int i = 0; i < N; ++i) {
std::cin >> A[i];
unique_vals_vec.push_back(A[i]);
}
// Coordinate Compression: Map large motivation values to smaller integer ranks [0, D-1]
std::sort(unique_vals_vec.begin(), unique_vals_vec.end());
// Remove duplicate values
unique_vals_vec.erase(std::unique(unique_vals_vec.begin(), unique_vals_vec.end()), unique_vals_vec.end());
std::map<long long, int> val_to_rank; // Map original value to its rank
std::vector<long long> rank_to_val(unique_vals_vec.size()); // Map rank back to original value
for (int i = 0; i < unique_vals_vec.size(); ++i) {
val_to_rank[unique_vals_vec[i]] = i;
rank_to_val[i] = i; // Store original value at index corresponding to its rank
}
// Corrected map: rank_to_val should store original value
for (int i = 0; i < unique_vals_vec.size(); ++i) {
rank_to_val[i] = unique_vals_vec[i];
}
int D = unique_vals_vec.size(); // Number of distinct motivation values
// Estimate initial size for the segment tree node pool. N * logN nodes approximately.
// Add a buffer factor for safety.
int initial_tree_size = std::max((int)(N * (std::log2(N+1) + 4)), 100); // Increased buffer
tree.resize(initial_tree_size);
roots.resize(N + 1); // Need N+1 roots for prefixes of length 0 to N
tree[0] = Node(); // Initialize the null node (node 0)
roots[0] = 0; // roots[0] corresponds to the empty prefix
// Build the persistent segment tree versions for all prefixes A[0...i]
for (int i = 0; i < N; ++i) {
// roots[i+1] is built upon roots[i] by adding element A[i]
roots[i+1] = update(roots[i], 0, D - 1, val_to_rank[A[i]]);
}
long long max_total_productivity = 0; // Stores the overall maximum productivity found
// Iterate through all possible team sizes K from 1 to N
for (int K = 1; K <= N; ++K) {
int M = N / K; // Maximum number of teams that can be formed consecutively from one end
if (M == 0) continue; // If N < K, we cannot form any team of size K
// L[p] stores the total productivity of the first p teams taken from the left
std::vector<long long> L(M + 1, 0);
// R[q] stores the total productivity of the first q teams taken from the right
std::vector<long long> R(M + 1, 0);
// Determine the rank 'k_th' needed for the median calculation based on K
// Problem statement definition: B[K/2] if K even, B[(K+1)/2] if K odd.
// This corresponds to the k-th smallest element where k = K/2 for even K, (K+1)/2 for odd K.
int k_th = (K % 2 == 1) ? (K + 1) / 2 : K / 2;
// Precompute L[p] values: Sum of productivities for teams taken from the left
for (int p = 1; p <= M; ++p) {
int start_idx = (p - 1) * K; // 0-based start index of the p-th block from left
int end_idx = p * K - 1; // 0-based end index of the p-th block from left
// Query the k-th element's rank in the range [start_idx, end_idx]
// Using roots[start_idx] (version before range) and roots[end_idx + 1] (version at end of range)
int rank = query(roots[start_idx], roots[end_idx + 1], 0, D - 1, k_th);
long long median_val = rank_to_val[rank]; // Map rank back to original value
// Add productivity of this team to the previous sum
L[p] = L[p-1] + (long long)K * median_val;
}
// Precompute R[q] values: Sum of productivities for teams taken from the right
for (int q = 1; q <= M; ++q) {
int start_idx = N - q * K; // 0-based start index of the q-th block from right
int end_idx = N - (q - 1) * K - 1; // 0-based end index of the q-th block from right
// Query the k-th element's rank in this range
int rank = query(roots[start_idx], roots[end_idx + 1], 0, D - 1, k_th);
long long median_val = rank_to_val[rank]; // Map rank back to original value
// Add productivity of this team to the previous sum
R[q] = R[q-1] + (long long)K * median_val;
}
// Compute MaxR[k] = max_{0 <= i <= k} R[i] using prefix maximums
std::vector<long long> MaxR(M + 1);
MaxR[0] = R[0]; // Base case: MaxR[0] = R[0] = 0
for (int q = 1; q <= M; ++q) {
// MaxR[q] is the maximum value found in R[0]...R[q]
MaxR[q] = std::max(MaxR[q-1], R[q]);
}
// Find the maximum possible total productivity for the current K
// Using the O(M) approach: max_{0 <= p <= M} (L[p] + MaxR[M-p])
long long current_K_max_prod = 0; // Max productivity for this K, initialized to 0 (option of forming no teams)
for (int p = 0; p <= M; ++p) {
int max_q_idx = M - p; // The maximum index for q such that p + q <= M
if (max_q_idx >= 0) { // Check if the index for MaxR is valid
// Calculate potential total productivity and update maximum for this K
current_K_max_prod = std::max(current_K_max_prod, L[p] + MaxR[max_q_idx]);
}
}
// Update the overall maximum productivity found across all K values
max_total_productivity = std::max(max_total_productivity, current_K_max_prod);
}
// Print the final result
std::cout << max_total_productivity << std::endl;
return 0;
}
qwewe