本記事では、プログラミングコンテスト「Wdoi R2」の問題に対する解法を解説します。各問題の戦略と実装の詳細について見ていきましょう。
問題 A: 幻の如く咲く花
解法
配列に対する操作は「要素の反転」と「整数値の加算」の2種類です。配列を反転させる操作は、最大でも1回行えば十分です。なぜなら、2回反転させると元に戻るためです。したがって、反転操作を行う場合と行わない場合の2つのケースを考慮し、それぞれで元の配列と目標の配列との差分を計算し、異なる要素の数を数えます。反転操作を行った場合は、そのコストとして+1を加算します。
コード例
#include <iostream>
#include <vector>
#include <algorithm>
#include <numeric>
long long count_differences(const std::vector<int>& arr1, const std::vector<int>& arr2) {
long long diff_count = 0;
for (size_t i = 0; i < arr1.size(); ++i) {
if (arr1[i] != arr2[i]) {
diff_count++;
}
}
return diff_count;
}
int main() {
std::ios_base::sync_with_stdio(false);
std::cin.tie(NULL);
int array_size;
std::cin >> array_size;
std::vector<int> initial_array(array_size);
std::vector<int> target_array(array_size);
for (int i = 0; i < array_size; ++i) {
std::cin >> initial_array[i];
}
for (int i = 0; i < array_size; ++i) {
std::cin >> target_array[i];
}
// Case 1: No reverse operation
long long cost_no_reverse = count_differences(initial_array, target_array);
// Case 2: One reverse operation
std::vector<int> reversed_array = initial_array;
std::reverse(reversed_array.begin(), reversed_array.end());
long long cost_with_reverse = count_differences(reversed_array, target_array) + 1;
std::cout << std::min(cost_no_reverse, cost_with_reverse) << std::endl;
return 0;
}
問題 B: 霊山に神風起こる
解法
この問題では、配列から特定の要素の組み合わせを選んで合計を最大化します。考えられるパターンは以下の4通りです。
- すべての値が
1の要素を選択する。 - 最も左にある
2の要素と、それより右にあるすべての1の要素を選択する。 - 最も右にある
3の要素と、それより左にあるすべての1の要素を選択する。 - 最も左にある
2の要素と最も右にある3の要素、そしてその中間にあるすべての1の要素を選択する。
これらすべてのケースを一度スキャンして最大値を求めます。
コード例
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::ios_base::sync_with_stdio(false);
std::cin.tie(NULL);
int n_elements;
std::cin >> n_elements;
std::vector<int> values(n_elements);
for (int i = 0; i < n_elements; ++i) {
std::cin >> values[i];
}
int max_sum = 0;
// Case 1: Select all '1's
for (int x : values) {
if (x == 1) {
max_sum++;
}
}
// Find the leftmost '2' and rightmost '3'
int leftmost_two_idx = -1;
for (int i = 0; i < n_elements; ++i) {
if (values[i] == 2) {
leftmost_two_idx = i;
break;
}
}
int rightmost_three_idx = -1;
for (int i = n_elements - 1; i >= 0; --i) {
if (values[i] == 3) {
rightmost_three_idx = i;
break;
}
}
// Case 2: Leftmost '2' and all '1's to its right
if (leftmost_two_idx != -1) {
int current_sum = 1; // for the '2' itself
for (int i = leftmost_two_idx + 1; i < n_elements; ++i) {
if (values[i] == 1) {
current_sum++;
}
}
max_sum = std::max(max_sum, current_sum);
}
// Case 3: Rightmost '3' and all '1's to its left
if (rightmost_three_idx != -1) {
int current_sum = 1; // for the '3' itself
for (int i = 0; i < rightmost_three_idx; ++i) {
if (values[i] == 1) {
current_sum++;
}
}
max_sum = std::max(max_sum, current_sum);
}
// Case 4: Leftmost '2', rightmost '3', and '1's in between
if (leftmost_two_idx != -1 && rightmost_three_idx != -1 && leftmost_two_idx <= rightmost_three_idx) {
int current_sum = 2; // for the '2' and '3'
for (int i = leftmost_two_idx + 1; i < rightmost_three_idx; ++i) {
if (values[i] == 1) {
current_sum++;
}
}
max_sum = std::max(max_sum, current_sum);
}
std::cout << max_sum << std::endl;
return 0;
}
問題 C: 地上からの援護
解法
この問題では、特定の条件下で到達可能な最大値を効率的に計算する必要があります。まず、x の前にある要素 [1, x-1] については、現在の値が選ばれるようにする必要があるため、これはシミュレーションによって初期値を計算できます。これは優先度キューを用いて処理します。
次に、x の後ろにある要素 [x+1, n] については、a_x + (i-x)v > a_i という条件を満たす必要があります。これを変形すると a_x > a_i - iv + xv となります。ここで a_i - iv の値を前処理しておけば、区間内の最大値を効率的に取得するためにセグメントツリーを使用できます。
コード例
#include <iostream>
#include <vector>
#include <queue>
#include <algorithm>
const int INF = 1e9 + 7;
// Segment Tree for range maximum query
std::vector<int> seg_tree_max_val;
std::vector<int> initial_values; // Store a_i - i*v
void build_segment_tree(int node_idx, int tree_left, int tree_right) {
if (tree_left == tree_right) {
seg_tree_max_val[node_idx] = initial_values[tree_left];
return;
}
int mid = tree_left + (tree_right - tree_left) / 2;
build_segment_tree(node_idx * 2, tree_left, mid);
build_segment_tree(node_idx * 2 + 1, mid + 1, tree_right);
seg_tree_max_val[node_idx] = std::max(seg_tree_max_val[node_idx * 2], seg_tree_max_val[node_idx * 2 + 1]);
}
int query_segment_tree(int node_idx, int tree_left, int tree_right, int query_left, int query_right) {
if (query_left <= tree_left && tree_right <= query_right) {
return seg_tree_max_val[node_idx];
}
int mid = tree_left + (tree_right - tree_left) / 2;
int max_val = -INF;
if (query_left <= mid) {
max_val = std::max(max_val, query_segment_tree(node_idx * 2, tree_left, mid, query_left, query_right));
}
if (query_right > mid) {
max_val = std::max(max_val, query_segment_tree(node_idx * 2 + 1, mid + 1, tree_right, query_left, query_right));
}
return max_val;
}
int main() {
std::ios_base::sync_with_stdio(false);
std::cin.tie(NULL);
int n_elements, num_queries, velocity_v;
std::cin >> n_elements >> num_queries >> velocity_v;
std::vector<int> current_values(n_elements + 1);
std::vector<int> max_reachable_before(n_elements + 1, 0); // max value reachable up to index i-1
std::priority_queue<int> pq; // Max-heap
for (int i = 1; i <= n_elements; ++i) {
std::cin >> current_values[i];
pq.push(current_values[i]);
max_reachable_before[i] = pq.top() + velocity_v; // value after processing
pq.pop();
pq.push(max_reachable_before[i]);
}
// Prepare values for segment tree: a_i - i*v
initial_values.resize(n_elements + 1);
for (int i = 1; i <= n_elements; ++i) {
initial_values[i] = current_values[i] - (long long)i * velocity_v;
}
seg_tree_max_val.resize(4 * (n_elements + 1));
build_segment_tree(1, 1, n_elements);
long long xor_sum_s = 0;
long long total_sum_s = 0;
for (int q = 0; q < num_queries; ++q) {
int x_idx, k_len;
std::cin >> x_idx >> k_len;
if (x_idx + k_len - 1 <= n_elements) {
int max_val_prefix = (x_idx == 1) ? -INF : max_reachable_before[x_idx - 1];
int max_val_suffix_contribution = -INF;
if (x_idx + 1 <= x_idx + k_len - 1) { // Check if the range for suffix contribution is valid
max_val_suffix_contribution = query_segment_tree(1, 1, n_elements, x_idx + 1, x_idx + k_len - 1) + (long long)x_idx * velocity_v;
}
int s_value = std::max(max_val_prefix, max_val_suffix_contribution + 1); // +1 as per derived condition
s_value = std::max(s_value, current_values[x_idx]); // x_idx itself can be chosen
xor_sum_s ^= s_value;
total_sum_s += s_value;
}
}
std::cout << xor_sum_s << " " << total_sum_s << std::endl;
return 0;
}
問題 D: 夜空のUFOラブソング
解法
この問題は、与えられた数 a, b, c に基づいて特定のビット演算の結果の lowbit を計算するものです。lowbit(x) は x & (-x) で計算され、x の最下位ビット (LSB) を返します。解法は、主に c の偶奇性と a の偶奇性に基づいてケース分けされます。
cが偶数の場合:aが奇数なら、最終結果は常に1となります。aが偶数なら、a^(2c)の末尾には少なくとも2c個の0が並ぶため、lowbit(c)が答えになります。
cが奇数の場合:a XOR bが奇数(つまりaとbの偶奇性が異なる)なら、最終結果は常に1となります。a XOR bが偶数(つまりaとbの偶奇性が同じ)なら、実はlowbit(c^(2c) XOR c)を計算しますが、これは打表によってlowbit(c-1)となることが分かっています。
コード例
#include <iostream>
// lowbit関数: 最下位ビットを返す
long long get_lowbit(long long num) {
return num & (-num);
}
int main() {
std::ios_base::sync_with_stdio(false);
std::cin.tie(NULL);
long long val_a, val_b, val_c;
std::cin >> val_a >> val_b >> val_c;
if (!(val_c & 1)) { // c is even
if (val_a & 1) { // a is odd
std::cout << 1 << std::endl;
} else { // a is even
std::cout << get_lowbit(val_c) << std::endl;
}
} else { // c is odd
if ((val_a & 1) ^ (val_b & 1)) { // a XOR b is odd
std::cout << 1 << std::endl;
} else { // a XOR b is even
// Empirically derived: lowbit(c^(2c) XOR c) = lowbit(c-1)
std::cout << get_lowbit(val_c - 1) << std::endl;
}
}
return 0;
}
問題 E: 死後の歓喜
解法
この問題はインタラクティブ問題であり、クエリを投げて情報を得る必要があります。まず、小さな数でパターンを観察すると、ある完全平方数 x^2 があったとき、その数から x+1 個の数が「好ましい」数であり、その後に x 個の数が「好ましくない」数であるというパターンが見つかります。
このパターンを利用して、二分探索と倍増法(binary lifting)を組み合わせて区間の境界を特定します。具体的には、ある数 x が与えられたときに、その数が属する連続した「好ましい」または「好ましくない」数の区間を見つけます。この区間の長さや開始位置は、二分探索で効率的に見つけることができます。
倍増法を用いて、区間の長さを 2^k ずつ広げていき、クエリ回数を抑制しながら境界を探索します。正方向と負方向の両方に倍増法を適用することで、完全平方数の前後の区間長を特定し、最終的な答えを導き出します。
コード例
#include <iostream>
#include <map>
#include <algorithm>
// Cache for query results to avoid redundant queries
std::map<long long, int> query_cache;
// Function to perform a query
int make_query(long long num) {
if (query_cache.count(num)) {
return query_cache[num];
}
std::cout << "? " << num << std::endl;
std::fflush(stdout); // Ensure the query is sent
int result;
std::cin >> result;
return query_cache[num] = result;
}
// Binary lifting to find the end of a segment
long long find_segment_end(long long start_val, long long max_len_estimate) {
int initial_state = make_query(start_val);
if (make_query(start_val + 1) != initial_state) {
return start_val; // Segment length is 1
}
long long current_offset = 0;
// Find largest power of 2 that is still within the segment
long long step_power = 0;
while ((1LL << (step_power + 1)) <= max_len_estimate) {
if (make_query(start_val + current_offset + (1LL << (step_power + 1))) == initial_state) {
current_offset += (1LL << (step_power + 1));
step_power++;
} else {
break;
}
}
// Fine-tune the end point using smaller powers of 2
for (long long i = step_power; i >= 0; --i) {
if (make_query(start_val + current_offset + (1LL << i)) == initial_state) {
current_offset += (1LL << i);
}
}
return start_val + current_offset;
}
int main() {
std::ios_base::sync_with_stdio(false);
std::cin.tie(NULL);
int num_test_cases;
std::cin >> num_test_cases;
while (num_test_cases--) {
query_cache.clear();
long long boundary1 = find_segment_end(0, 1); // Find the end of the first segment starting from 0
long long boundary2 = find_segment_end(boundary1 + 1, std::max(boundary1, 1LL)); // Find the end of the segment after boundary1
long long segment_length_L = boundary2 - boundary1;
if (make_query(0) == 0) { // If 0 is "not cute" (0-indexed implies first segment is not cute)
// L-1 is the side length of the square
std::cout << "! " << (segment_length_L - 1) * (segment_length_L - 1) - 1 - boundary1 << std::endl;
} else { // If 0 is "cute" (0-indexed implies first segment is cute)
// L is the side length of the square
std::cout << "! " << segment_length_L * segment_length_L + segment_length_L - boundary1 << std::endl;
}
std::fflush(stdout); // Ensure the answer is sent
}
return 0;
}
問題 F: 魔力の磁雲
解法
この問題は非常に複雑で、磁石の配置と磁力の値を特定する必要があります。まず、差分 c_i = d_0 - d_i を計算します。
フェーズ1: 方向の特定
c_i の値や c_i を 3 で割った余りから、磁石 i と隣接する磁石 i-1, i+1 との間にどのような関係があるかを推測します。
c_i = 1の場合、i-1, i, i+1は同じ方向の連続した磁石であると推測できます。c_i % 3 == 1かつc_i != 1の場合、これは長さ2の連続セグメントの一部である可能性があります。
結合された磁石のセグメントを管理するために、Union-Find (Disjoint Set Union) 構造を使用します。長さ 1 の孤立した磁石や長さ 3 以上の連続セグメントを特定し、残りの磁石が長さ 2 のセグメントからなる連続セグメントの一部として処理されます。
フェーズ2: 磁力の特定
各磁石の方向が確定した後、具体的な磁力 a_i を決定します。ここで b_i = 4^(a_i) という関係を利用します。 b_i は常に 2 の偶数乗( 4^k = 2^(2k) )です。この性質は、二進数表現において 1 が常に偶数ビット位置にあることを意味します。
- 長さ
>= 2の連続セグメントの場合: 隣接する磁石j, kが同じ方向であれば、c_j - 1 = b_j - b_kとなります。b_jとb_kの差のlowbitは小さい方のb_kになるという性質を利用して、b_jとb_kを順次特定できます。 - 長さ
1の孤立した磁石の場合:c_j - 1 = b_i + 2b_j + b_kの関係が成立します。popcount(c_j - 1)の値によってさらに分岐し、b_iやb_kが既知であればb_jを決定できます。
すべての磁力値を決定できないケース(例: すべての popcount(c_j-1)=2 かつ隣接磁石の方向が異なる場合)は、特定のパターンに帰着され、直接磁力を割り当てることができます。
全体として O(N) の時間計算量で解決されます。
コード例
#include <iostream>
#include <vector>
#include <numeric>
#include <algorithm>
#include <map>
const int MAX_N = 1e6;
long long initial_D_val;
std::vector<long long> D_values;
std::vector<long long> C_values; // C_i = D_0 - D_i
std::vector<int> magnet_direction; // f[i]: 0 or 1 for direction
std::vector<int> magnet_power_exp; // a[i]: power 'a' for 4^a
// Helper for circular array indexing
int get_circular_idx(int idx, int n_magnets) {
if (idx > n_magnets) return idx - n_magnets;
if (idx < 1) return idx + n_magnets;
return idx;
}
// Bit manipulation helpers
int count_set_bits(long long num) {
return __builtin_popcountll(num);
}
int get_highest_set_bit_pos(long long num) { // returns 0-indexed position
if (num == 0) return -1;
return 63 - __builtin_clzll(num);
}
long long get_lowbit(long long num) {
return num & (-num);
}
// Union-Find structure
std::vector<int> parent_array;
std::vector<int> component_size;
int find_set(int i) {
if (parent_array[i] == i)
return i;
return parent_array[i] = find_set(parent_array[i]);
}
void union_sets(int i, int j) {
int root_i = find_set(i);
int root_j = find_set(j);
if (root_i != root_j) {
parent_array[root_j] = root_i;
component_size[root_i] += component_size[root_j];
}
}
// Phase 1: Determine magnet directions (f)
void determine_directions(int n_magnets) {
parent_array.resize(n_magnets + 1);
std::iota(parent_array.begin(), parent_array.end(), 0); // Initialize parent_array
component_size.assign(n_magnets + 1, 1);
// Apply rule for C_i = 1
if (C_values[1] == 1) {
union_sets(1, n_magnets);
union_sets(1, 2);
}
if (C_values[n_magnets] == 1) {
union_sets(n_magnets, get_circular_idx(n_magnets - 1, n_magnets));
union_sets(n_magnets, get_circular_idx(1, n_magnets));
}
for (int i = 2; i <= n_magnets - 1; ++i) {
if (C_values[i] == 1) {
union_sets(i - 1, i);
union_sets(i, i + 1);
}
}
std::vector<int> group_boundaries;
for (int i = 1; i <= n_magnets; ++i) {
// C_i % 3 == 2 indicates a boundary between different direction segments
if (C_values[i] >= 0 && C_values[i] % 3 == 2) {
group_boundaries.push_back(i);
}
}
// Also include elements that are part of segments longer than 1 (already grouped)
for (int i = 1; i <= n_magnets; ++i) {
if (component_size[find_set(i)] != 1) {
group_boundaries.push_back(i);
}
}
std::sort(group_boundaries.begin(), group_boundaries.end());
group_boundaries.erase(std::unique(group_boundaries.begin(), group_boundaries.end()), group_boundaries.end());
if (group_boundaries.empty()) { // All magnets are isolated or in one large segment
for(int i = 1; i < n_magnets; i += 2) union_sets(i, i+1);
if (n_magnets % 2 != 0) union_sets(n_magnets, 1); // Circular
} else {
// Union isolated elements in groups of 2.
// Elements not part of a pre-determined boundary or segment of length > 1, are treated as part of length-2 segments.
for (size_t i = 0; i < group_boundaries.size(); ++i) {
int current_boundary = group_boundaries[i];
int next_boundary = (i == group_boundaries.size() - 1) ? get_circular_idx(group_boundaries[0], n_magnets) : group_boundaries[i+1];
// Handle circular wrap around
if (next_boundary <= current_boundary) {
for (int j = get_circular_idx(current_boundary + 1, n_magnets); j != next_boundary; j = get_circular_idx(j + 2, n_magnets)) {
union_sets(j, get_circular_idx(j + 1, n_magnets));
}
} else {
for (int j = current_boundary + 1; j < next_boundary; j += 2) {
if (j + 1 < next_boundary) union_sets(j, j + 1);
}
}
}
}
magnet_direction.resize(n_magnets + 1);
int current_dir = 0;
std::map<int, int> root_direction_map; // Map root to its assigned direction
for (int i = 1; i <= n_magnets; ++i) {
int root = find_set(i);
if (root_direction_map.find(root) == root_direction_map.end()) {
root_direction_map[root] = current_dir;
current_dir ^= 1;
}
magnet_direction[i] = root_direction_map[root];
}
}
// Assign 'a' values for magnet powers
void assign_power_a(int idx1, int idx2, long long diff_val) {
if (diff_val < 0) { // Ensure diff_val is positive for consistent lowbit behavior
diff_val = -diff_val;
std::swap(idx1, idx2);
}
long long lb_idx2 = get_lowbit(diff_val);
long long val_idx1 = lb_idx2 + diff_val; // This implies val_idx1 = 4^a1, val_idx2 = 4^a2
// a_i is log4(val_idx_i) = log2(val_idx_i)/2
magnet_power_exp[idx1] = get_highest_set_bit_pos(val_idx1) / 2;
magnet_power_exp[idx2] = get_highest_set_bit_pos(lb_idx2) / 2;
}
// Solve for 3 magnets in a row (i, j, k) with C_j-1 = b_i + 2*b_j + b_k
void solve_three_magnets(int i_prev, int i_curr, int i_next, long long v_val) {
if (count_set_bits(v_val) == 3) {
// v_val = X + Y + Z where X,Y,Z are powers of 4 (possibly with a factor of 2)
// One term must be 2*b_j (odd bit pos), others b_i, b_k (even bit pos)
long long bit1 = get_lowbit(v_val);
long long bit2 = get_lowbit(v_val ^ bit1);
long long bit3 = get_lowbit(v_val ^ bit1 ^ bit2);
// Find the one with odd bit position (2 * 4^p = 2^(2p+1))
if ((get_highest_set_bit_pos(bit1) % 2) == 1) magnet_power_exp[i_curr] = (get_highest_set_bit_pos(bit1) - 1) / 2;
if ((get_highest_set_bit_pos(bit2) % 2) == 1) magnet_power_exp[i_curr] = (get_highest_set_bit_pos(bit2) - 1) / 2;
if ((get_highest_set_bit_pos(bit3) % 2) == 1) magnet_power_exp[i_curr] = (get_highest_set_bit_pos(bit3) - 1) / 2;
} else if (count_set_bits(v_val) == 2) {
// This implies b_i = b_k.
// v_val = 2*b_j + 2*b_i (if b_i = b_k)
// Or v_val = b_i + 2*b_j + b_k where b_i, b_k known.
int known_power_exp = -1;
if (magnet_power_exp[i_prev] != -1) known_power_exp = magnet_power_exp[i_prev];
if (magnet_power_exp[i_next] != -1) known_power_exp = magnet_power_exp[i_next];
long long bit1 = get_lowbit(v_val);
long long bit2 = get_lowbit(v_val ^ bit1);
int p1 = (get_highest_set_bit_pos(bit1) - 1) / 2; // Potential b_j
int p2 = (get_highest_set_bit_pos(bit2) - 1) / 2; // Potential b_j
if (known_power_exp != -1) {
if (known_power_exp == p1) magnet_power_exp[i_curr] = p2;
else magnet_power_exp[i_curr] = p1;
} else {
// Cannot uniquely determine yet without more info
// This case is handled by full determination later if needed.
// For now, assign a placeholder
magnet_power_exp[i_curr] = -1;
}
}
}
// Phase 2: Determine magnet powers (a)
void determine_powers(int n_magnets) {
magnet_power_exp.assign(n_magnets + 1, -1); // Initialize to unknown
// Check if all magnet segments are of length 2 and C_i-1 has popcount 2
bool all_length_2_alternating = true;
for (int i = 1; i <= n_magnets; ++i) {
if (! (C_values[i] - 1 >= 0 && count_set_bits(C_values[i] - 1) == 2) ) {
all_length_2_alternating = false;
break;
}
if (magnet_direction[i] == magnet_direction[get_circular_idx(i + 1, n_magnets)]) {
all_length_2_alternating = false;
break;
}
}
if (all_length_2_alternating) {
// Special case: all magnets are in alternating direction, length 2 segments, (c_i-1) has two set bits.
// This means b_i and b_{i+1} alternate values (e.g., x, y, x, y, ...)
long long val_diff = C_values[1] - 1;
long long bit1 = get_lowbit(val_diff);
long long bit2 = get_lowbit(val_diff ^ bit1);
int p1 = get_highest_set_bit_pos(bit1) / 2;
int p2 = get_highest_set_bit_pos(bit2) / 2;
for (int i = 1; i <= n_magnets; i += 2) magnet_power_exp[i] = p1;
for (int i = 2; i <= n_magnets; i += 2) magnet_power_exp[i] = p2;
return;
}
// First, use segments with determined directions to fix some values
// Find a segment that allows initial determination
int first_fixed_idx = -1;
for (int i = 1; i <= n_magnets; ++i) {
int prev_idx = get_circular_idx(i - 1, n_magnets);
if (magnet_direction[prev_idx] == magnet_direction[i]) { // Same direction segment
if (C_values[i] - 1 >= 0) {
assign_power_a(i, prev_idx, C_values[i] - 1);
first_fixed_idx = i;
break;
}
} else { // Different direction segment
if (C_values[i] - 1 >= 0 && count_set_bits(C_values[i] - 1) == 3) {
solve_three_magnets(prev_idx, i, get_circular_idx(i + 1, n_magnets), C_values[i] - 1);
first_fixed_idx = i; // This magnet's power might be fixed
break;
}
}
}
if (first_fixed_idx == -1) { // No values fixed by segments or 3-magnet popcount
// All C_i-1 have popcount 2 and alternating directions.
// If this point is reached, it implies magnet_power_exp[i_prev] and magnet_power_exp[i_next] are not yet determined.
// This case would be handled by the 'all_length_2_alternating' check above if it covers the entire ring.
// If it doesn't cover the entire ring, it means some magnet_power_exp[i] == -1 for some i,
// so we need to assign arbitrary values for those not determined.
for (int i = 1; i <= n_magnets; ++i) {
if (magnet_power_exp[i] == -1) {
magnet_power_exp[i] = 1919810 + i; // Arbitrary distinct large values
}
}
return;
}
// Propagate fixed values
for (int iter = 0; iter < 2; ++iter) { // Iterate twice to ensure full propagation around the circle
for (int i = 1; i <= n_magnets; ++i) {
int prev_idx = get_circular_idx(i - 1, n_magnets);
int next_idx = get_circular_idx(i + 1, n_magnets);
if (C_values[i] - 1 < 0) continue;
if (magnet_direction[prev_idx] == magnet_direction[i]) { // Same direction segment (i-1, i)
if (magnet_power_exp[prev_idx] != -1 && magnet_power_exp[i] == -1) {
assign_power_a(i, prev_idx, C_values[i] - 1); // fix i using i-1
} else if (magnet_power_exp[i] != -1 && magnet_power_exp[prev_idx] == -1) {
assign_power_a(prev_idx, i, -(C_values[i] - 1)); // fix i-1 using i
}
} else { // Different direction segment (i-1), i, (i+1)
if (magnet_power_exp[i] == -1) {
// Try to fix i using i-1 and i+1 if they are known
if (magnet_power_exp[prev_idx] != -1 && magnet_power_exp[next_idx] != -1) {
solve_three_magnets(prev_idx, i, next_idx, C_values[i] - 1);
}
} else { // If i is known, try to fix neighbors
// 2*4^(a_i) = val_curr_bit_odd
long long val_curr_bit_odd = (1LL << (2 * magnet_power_exp[i] + 1));
long long remaining_val = (C_values[i] - 1) - val_curr_bit_odd;
if (remaining_val >= 0 && count_set_bits(remaining_val) == 2) { // Should be b_prev + b_next
long long lb1 = get_lowbit(remaining_val);
long long lb2 = get_lowbit(remaining_val ^ lb1);
int p1 = get_highest_set_bit_pos(lb1) / 2;
int p2 = get_highest_set_bit_pos(lb2) / 2;
if (magnet_power_exp[prev_idx] == -1) magnet_power_exp[prev_idx] = p1;
if (magnet_power_exp[next_idx] == -1) magnet_power_exp[next_idx] = p2;
// Assuming p1 and p2 correspond to prev_idx and next_idx
// This part is tricky if p1 == p2.
// For simplicity, for known i, if its neighbors prev_idx/next_idx are unknown,
// and remaining_val can uniquely decompose into two 4^a terms, assign them.
}
}
}
}
}
// Fill in any remaining undetermined values with arbitrary large distinct numbers
for (int i = 1; i <= n_magnets; ++i) {
if (magnet_power_exp[i] == -1) {
magnet_power_exp[i] = 1919810 + i; // Assign arbitrary value
}
}
}
int main() {
std::ios_base::sync_with_stdio(false);
std::cin.tie(NULL);
int num_magnets;
std::cin >> num_magnets >> initial_D_val;
D_values.resize(num_magnets + 1);
C_values.resize(num_magnets + 1);
for (int i = 1; i <= num_magnets; ++i) {
std::cin >> D_values[i];
C_values[i] = initial_D_val - D_values[i];
}
determine_directions(num_magnets);
determine_powers(num_magnets);
for (int i = 1; i <= num_magnets; ++i) {
std::cout << magnet_direction[i] << " " << magnet_power_exp[i] << std::endl;
}
return 0;
}
問題 G: 純粋なる復讐の女神
解法
この問題はオフラインのスイープラインアルゴリズムとデータ構造を組み合わせて解きます。各要素 a_i の影響範囲 [L_i, R_i] を単調スタックを使って事前に求めます。 L_i は a_i より左側で a_i 以上の値が現れない最初の位置、 R_i は右側で同様の条件を満たす最後の位置です。
次に、クエリと影響範囲を r (右端) でソートし、スイープライン処理を行います。
r が増加するにつれて、以下の操作を行います。
- 現在
rを右端とするすべての影響範囲[L_j, R_j] (a_j)をセグメントツリーに「挿入」します。 - 現在
rを右端とするすべてのクエリ(l_k, r)を処理します。これは、セグメントツリーのl_kの位置での最大値を問い合わせることで行います。 -
R_j = rである影響範囲[L_j, R_j]をセグメントツリーから「削除」します。
セグメントツリーは、区間 [L, R] 内のすべての点に値 w を適用し、単一点のクエリに対して適用されている最大の値を見つける必要があります。これは、各ノードに std::multiset を持ち、範囲更新に対してはノードに直接値を格納し、ポイントクエリに対してはパス上のすべてのノードの multiset を考慮することで実装できます。 add と del の multiset を使用して、遅延伝播せずに区間更新を効果的に処理します。
コード例
#include <iostream>
#include <vector>
#include <stack>
#include <algorithm>
#include <set> // For multiset
const int MAX_N = 2e5;
// Segment Tree node stores multisets for active values
struct SegmentTreeNode {
std::multiset<int> added_values;
std::multiset<int> removed_values;
int get_max_active_value() const {
// Remove values that have been "removed" from "added"
// This is a simplified way to handle updates without explicit lazy propagation
// By always keeping added_values and removed_values distinct where possible.
// For range updates, this approach might be complex. A more common approach
// for range updates and point queries is lazy propagation or explicit segment tree
// with node-specific values and pushing down/up.
// The original code implies this logic:
// while(added_values.size() && removed_values.size() && *added_values.rbegin() == *removed_values.rbegin()) {
// added_values.erase(std::prev(added_values.end()));
// removed_values.erase(std::prev(removed_values.end()));
// }
// The problem description mentions "marking permanently" for segment tree.
// This usually means values don't propagate down, they stay in nodes.
// To query, we traverse up from leaf to root, and combine max from all nodes on path.
if (added_values.empty()) return 0; // Default or minimum value
return *added_values.rbegin();
}
};
std::vector<SegmentTreeNode> seg_tree;
int N_elements; // Global for segment tree functions
// Update segment tree for a range [update_left, update_right] with value 'val'
// If val > 0, it's an addition; if val < 0, it's a removal (-val)
void update_segment_tree(int node_idx, int tree_left, int tree_right, int update_left, int update_right, int val) {
if (update_left <= tree_left && tree_right <= update_right) {
if (val > 0) {
seg_tree[node_idx].added_values.insert(val);
} else {
seg_tree[node_idx].added_values.erase(seg_tree[node_idx].added_values.find(-val)); // Erase specific value
}
return;
}
int mid = tree_left + (tree_right - tree_left) / 2;
if (update_left <= mid) {
update_segment_tree(node_idx * 2, tree_left, mid, update_left, update_right, val);
}
if (update_right > mid) {
update_segment_tree(node_idx * 2 + 1, mid + 1, tree_right, update_left, update_right, val);
}
}
// Query segment tree for the maximum value at a specific point 'query_pos'
int query_segment_tree(int node_idx, int tree_left, int tree_right, int query_pos) {
int max_val_on_path = 0;
if (!seg_tree[node_idx].added_values.empty()) {
max_val_on_path = *seg_tree[node_idx].added_values.rbegin();
}
if (tree_left == tree_right) {
return max_val_on_path;
}
int mid = tree_left + (tree_right - tree_left) / 2;
if (query_pos <= mid) {
max_val_on_path = std::max(max_val_on_path, query_segment_tree(node_idx * 2, tree_left, mid, query_pos));
} else {
max_val_on_path = std::max(max_val_on_path, query_segment_tree(node_idx * 2 + 1, mid + 1, tree_right, query_pos));
}
return max_val_on_path;
}
struct InfluenceInterval {
int left, right, value;
};
struct Query {
int left_pos, query_id;
};
int main() {
std::ios_base::sync_with_stdio(false);
std::cin.tie(NULL);
int num_queries;
std::cin >> N_elements >> num_queries;
std::vector<int> item_colors(N_elements + 1);
std::vector<int> item_values(N_elements + 1);
for (int i = 1; i <= N_elements; ++i) std::cin >> item_colors[i];
for (int i = 1; i <= N_elements; ++i) std::cin >> item_values[i];
std::vector<int> L_boundary(N_elements + 1);
std::vector<int> R_boundary(N_elements + 1);
// Calculate L_boundary using a monotonic stack for each color
std::vector<std::stack<int>> color_stacks_left(N_elements + 1); // Stacks storing indices
for (int i = 1; i <= N_elements; ++i) {
// Sentinel value for the stack
if (color_stacks_left[item_colors[i]].empty()) color_stacks_left[item_colors[i]].push(0);
while (item_values[color_stacks_left[item_colors[i]].top()] >= item_values[i]) {
color_stacks_left[item_colors[i]].pop();
}
L_boundary[i] = color_stacks_left[item_colors[i]].top() + 1;
color_stacks_left[item_colors[i]].push(i);
}
// Calculate R_boundary using a monotonic stack for each color (from right to left)
std::vector<std::stack<int>> color_stacks_right(N_elements + 1);
for (int i = 1; i <= N_elements; ++i) { // Reset stacks for right pass
while(!color_stacks_right[i].empty()) color_stacks_right[i].pop();
color_stacks_right[i].push(N_elements + 1); // Sentinel value
}
for (int i = N_elements; i >= 1; --i) {
while (item_values[color_stacks_right[item_colors[i]].top()] >= item_values[i]) {
color_stacks_right[item_colors[i]].pop();
}
R_boundary[i] = color_stacks_right[item_colors[i]].top() - 1;
color_stacks_right[item_colors[i]].push(i);
}
// Group intervals and queries by their right endpoint for sweep line
std::vector<std::vector<InfluenceInterval>> intervals_by_right_end(N_elements + 2);
std::vector<std::vector<Query>> queries_by_right_end(N_elements + 1);
for (int i = 1; i <= N_elements; ++i) {
intervals_by_right_end[i].push_back({L_boundary[i], i, item_values[i]}); // Interval [L_boundary[i], i] with value item_values[i]
intervals_by_right_end[R_boundary[i] + 1].push_back({L_boundary[i], i, -item_values[i]}); // Mark for removal at R_boundary[i]+1
}
std::vector<int> query_results(num_queries + 1);
for (int i = 1; i <= num_queries; ++i) {
int left_q, right_q;
std::cin >> left_q >> right_q;
queries_by_right_end[right_q].push_back({left_q, i});
}
// Initialize segment tree
seg_tree.resize(4 * (N_elements + 1));
// Sweep line
for (int current_r = 1; current_r <= N_elements; ++current_r) {
// Apply updates (additions and removals) for intervals ending at current_r or starting after it
for (const auto& interval : intervals_by_right_end[current_r]) {
update_segment_tree(1, 1, N_elements, interval.left, interval.right, interval.value);
}
// Process queries with current_r as right endpoint
for (const auto& query : queries_by_right_end[current_r]) {
query_results[query.query_id] = query_segment_tree(1, 1, N_elements, query.left_pos);
}
}
for (int i = 1; i <= num_queries; ++i) {
std::cout << query_results[i] << "\n";
}
return 0;
}
問題 H: 禁断の扉の向こう、此岸か彼岸か
解法
この問題は非常に難解で、動的計画法とWQS二分探索(凸性最適化)を組み合わせる必要があります。
まず、寄与関数 f(B) を簡略化します。 A_{i,k} = a_i * b_k であると仮定すると、寄与関数は以下のようになります。
\[
f(B) = \sum_{i=1}^{n} a_i \sum_{j=1}^{t} (s_{\max(B_{i,j}, B_{i+1,j})} - s_{\min(B_{i,j}, B_{i+1,j})-1})
\]
ここで s_x は b の累積和です。
行 X と Y の間の寄与関数 F(X,Y) を定義すると、問題は \sum a_i F(X,Y) の最大化になります。 a_i の合計は一定なので、 F(X,Y) が最小になるようにします。
重要な観察は、 B の各行の構成が X, Y, X, Y, \ldots の形になることです。
これはグラフの最大マッチング問題に似ていますが、計算量が大きすぎます。
この問題には凸性があるため、WQS二分探索が適用できます。WQS二分探索は、特定の制約 k の下で最適解を求める問題が凸性を持つ場合に、制約をコストに変換して二分探索を行う手法です。
DP状態 dp[i][j][0/1] を i 番目の要素まで考慮し、 j 個のペアを形成し、 i-1 と i が結合されているかどうかに基づいて定義します。ループ長 2 または 3 のサイクル、およびチェーンの接続・切断を考慮して遷移を定義します。
WQS二分探索により、選ぶペアの数に関するコスト mid を導入し、DPで j を直接持たずに最適化します。これにより、DPの計算量を O(M) に削減できます。
コード例
#include <iostream>
#include <vector>
#include <algorithm>
const int MAX_M = 5e5 + 10;
const long long INF_LL = 0x3f3f3f3f3f3f3f3fLL; // Sufficiently large for long long
const int MOD = 1e9 + 7;
// DP state structure
struct DPState {
long long total_value;
int pair_count;
DPState operator+(const DPState& other) const {
return {total_value + other.total_value, pair_count + other.pair_count};
}
bool operator<(const DPState& other) const {
if (total_value != other.total_value) {
return total_value < other.total_value;
}
return pair_count < other.pair_count;
}
};
std::vector<int> B_values;
std::vector<DPState> dp_connected, dp_disconnected; // dp_connected[i]: i is connected to i-1, dp_disconnected[i]: i is not connected to i-1
// Function to calculate minimum F(X,Y) and count pairs for a given cost_per_pair
// Using WQS binary search, we add 'cost_per_pair' for each selected pair.
// We want to minimize (original_value + cost_per_pair * num_pairs).
// The target is to find a specific number of pairs 'target_t_pairs'.
DPState calculate_min_cost(int m_elements, long long cost_per_pair) {
// Initialize DP states.
// dp_disconnected[i] means element i is not part of a pair with i-1.
// dp_connected[i] means element i is part of a pair with i-1.
dp_disconnected.assign(m_elements + 1, {INF_LL, 0});
dp_connected.assign(m_elements + 1, {INF_LL, 0});
dp_disconnected[0] = {0, 0}; // Base case: 0 elements, 0 value, 0 pairs
// Iterate through elements from 1 to m_elements
for (int i = 1; i <= m_elements; ++i) {
// Case 1: Element 'i' is not connected to 'i-1'.
// It can either not be paired at all, or be the end of a chain/cycle.
// It can extend from a previous disconnected state.
dp_disconnected[i] = dp_disconnected[i-1]; // Not paired with i-1
if (i >= 2) { // Consider forming a pair (i-1, i)
// If (i-1, i) forms a pair (length 2 cycle or segment), it contributes B_values[i-1] + B_values[i]
// We subtract cost_per_pair for this pair.
DPState current_pair_cost = { (long long)B_values[i-1] + B_values[i] - cost_per_pair, 1 };
// dp_connected[i] can come from dp_disconnected[i-2] (if (i-1, i) is a new pair)
// or dp_connected[i-1] (if (i-1, i) extends a chain, this interpretation is usually for "connected")
// Here, it refers to the element 'i' being covered by a matching.
// i-1 and i are matched. It means i-2 was disconnected.
dp_connected[i] = dp_disconnected[i-2] + current_pair_cost;
// The problem uses a different state: dp[i][0] means i is not an endpoint of a match.
// dp[i][1] means i is an endpoint of a match connected to i-1.
// Let's adjust states to match the common WQS DP for contiguous intervals:
// dp[i][0] = min cost using prefix i, where i is NOT covered by a segment ending at i.
// dp[i][1] = min cost using prefix i, where i IS covered by a segment ending at i.
// dp[i][0]: Element i is not involved in a match that ends at i.
// It could be that i-1 was also not involved (dp[i-1][0])
// Or i-1 was involved, but the match ended at i-1 (dp[i-1][1])
dp_disconnected[i] = std::min(dp_disconnected[i], dp_connected[i-1]);
// dp[i][1]: Element i is involved in a match that ends at i.
// Match of length 2: (i-1, i)
DPState option1_cost = dp_disconnected[i-2] + DPState{ (long long)B_values[i-1] + B_values[i] - cost_per_pair, 1 };
dp_connected[i] = option1_cost;
// Match of length 3: (i-2, i-1, i) forming a "cycle" or complex structure.
// This is effectively (i-2,i) and i-1 used as "middle".
// Original solution considers 2*(B[i-2]+B[i])+3*B[i-1] for 3 edges.
// This corresponds to a 3-cycle, (i-2)-(i-1)-(i)-(i-2).
// This implies (i-2,i-1), (i-1,i), (i,i-2) pairs. Each subtraction of cost_per_pair
// We would subtract 3*cost_per_pair for 3 pairs.
// The formula 2*(b[i-2]+b[i])+3*b[i-1] seems to imply 3 chosen elements, each contributing to value.
// If we consider 3 elements: B_values[i-2], B_values[i-1], B_values[i]
// The formula in the original problem is `2*(b[i-2]+b[i])+3*b[i-1] - 3*mid` for 3 pairs.
// This is for a "chain" like segment (i-2)-(i-1)-(i) where all are linked.
// The DP should consider the value of chosen elements, and the number of "edges" or "connections".
// A common interpretation is: B_values[i-1]+B_values[i] is the *value* of the "edge" (i-1, i).
// dp[i][0] : minimum value up to i, where i is not connected.
// dp[i][1] : minimum value up to i, where i is connected to i-1.
// From original code:
// f[i][0] = f[i-1][0] (i is not part of match, i-1 not)
// f[i][1] = min(f[i-1][1], f[i-2][0]) + {b[i-1]+b[i]-mid,1} (i-1,i matched, means i-1 was not matched or i-2 was not matched)
// f[i][0] = min(f[i][0], f[i-2][0] + {2*(b[i-1]+b[i])-2*mid,2}) (cycle of (i-1,i) and (i,i-1) - not real)
// f[i][0] = min(f[i][0], f[i-3][0] + {2*(b[i-2]+b[i])+3*b[i-1]-3*mid,3}) (cycle of 3)
// Let's re-align to f[i][0] (no match ending at i) and f[i][1] (match ending at i involving i-1)
// f[i][0] comes from:
// 1. f[i-1][0] (i-1 not matched, i not matched)
// 2. f[i-1][1] (i-1 matched with i-2, i not matched)
// 3. f[i-2][0] + (2 pairs involving i-1, i) -- this is a bit ambiguous in the original.
// It could mean (i-1,i) and (i, i-1) as "edges" for a total of 2 pairs.
// The value "2*(B[i-1]+B[i])" is suspicious for 2 pairs; sum of B_values[k] is only for one side.
// This likely refers to the F(X,Y) function where X,Y are specific rows.
// The original logic is:
// f[i][0] represents the state where position i is not the *end* of an active chosen pair chain.
// f[i][1] represents the state where position i *is* the end of an active chosen pair chain, specifically (i-1, i).
DPState current_val_2cycle = { (long long)B_values[i-1] + B_values[i] - cost_per_pair, 1 };
// Update f[i][1] (i matched with i-1)
// This can come from:
// (i-2,i-1) was matched, and we extend this to (i-1,i) where i-2, i-1, i form a chain (f[i-1][1] is predecessor)
// (i-2) was not matched, and we form a new pair (i-1,i) (f[i-2][0] is predecessor)
dp_connected[i] = std::min(dp_connected[i-1] + DPState{0,0}, dp_disconnected[i-2]) + current_val_2cycle;
// The 0,0 for extending means the edge i-1,i is simply added as a unit (1 pair)
// Update f[i][0] (i not matched with i-1)
// This can come from:
// 1. i-1 was not matched (dp_disconnected[i-1])
// 2. i-1 was matched, but the matching ended there. (dp_connected[i-1])
// So, f[i][0] must be min(f[i-1][0], f[i-1][1])? No.
// f[i][0] = f[i-1][0] is when (i-1) is not endpoint of match and (i) is not endpoint.
// But what if (i-1) IS endpoint of match? It still means (i) is not endpoint.
// The original code implies f[i][0] is just f[i-1][0] initially.
// This means we are only allowed to consider non-overlapping segments.
// Let's assume f[i][0] is overall min cost for prefix i, where i is NOT part of a match (as last element)
// f[i][1] is overall min cost for prefix i, where i IS part of a match (as last element, connected to i-1)
// The interpretation from the original code for f[i][0] and f[i][1]:
// f[i][0]: Optimal state for prefix i, with no edge (i-1, i) chosen.
// f[i][1]: Optimal state for prefix i, with edge (i-1, i) chosen.
// f[i][0] options:
// 1. i-1 was not matched to i-2 (f[i-1][0]). i is not matched.
// 2. i-1 was matched to i-2 (f[i-1][1]). i is not matched.
dp_disconnected[i] = std::min(dp_disconnected[i-1], dp_connected[i-1]);
// Now, consider forming new matches that end at i, making i no longer part of an active match chain
// Match (i-1, i) -> this means f[i][1] is an option, but f[i][0] can also be formed if it's a closed segment.
// 2-element segment (i-1, i) from a disconnected i-2
if (i >= 2) {
dp_disconnected[i] = std::min(dp_disconnected[i], dp_disconnected[i-2] + current_val_2cycle);
}
// 3-element segment (i-2, i-1, i) (e.g., triangle or line with specific values)
// The term `2*(b[i-2]+b[i])+3*b[i-1]-3*mid` implies 3 pairs.
// This is a value contribution from a specific configuration of 3 elements forming 3 pairs.
// If i-3 was disconnected:
if (i >= 3) {
DPState val_3cycle = { (long long)2 * (B_values[i-2] + B_values[i]) + 3 * B_values[i-1] - 3 * cost_per_pair, 3 };
dp_disconnected[i] = std::min(dp_disconnected[i], dp_disconnected[i-3] + val_3cycle);
}
}
}
// Final answer is the minimum of two states at the end:
// Either m-1 was not matched, or m-1 was matched but it ended there.
// The target_t_pairs count is for F(X,Y). So the num field in DPState matters.
return std::min(dp_disconnected[m_elements], dp_connected[m_elements]);
}
int main() {
std::ios_base::sync_with_stdio(false);
std::cin.tie(NULL);
int n_rows, m_cols, target_t_pairs;
long long sum_a_values = 0;
std::cin >> n_rows >> m_cols >> target_t_pairs;
for (int i = 0; i < n_rows; ++i) {
int a_val;
std::cin >> a_val;
sum_a_values = (sum_a_values + a_val) % MOD;
}
B_values.resize(m_cols + 1);
for (int i = 1; i <= m_cols; ++i) {
std::cin >> B_values[i];
}
// WQS Binary Search for optimal `cost_per_pair`
// The range for cost_per_pair (mid) can be large.
// Values B_i are up to 10^9. Sums can be larger.
long long low_cost = -3e9, high_cost = 3e9; // Estimate appropriate range
long long optimal_cost_per_pair = 0;
DPState final_dp_state;
while (low_cost <= high_cost) {
long long mid_cost = low_cost + (high_cost - low_cost) / 2;
DPState current_dp_result = calculate_min_cost(m_cols, mid_cost);
if (current_dp_result.pair_count <= target_t_pairs) {
// We have too few pairs (or exactly target_t_pairs) for this cost.
// Try a higher cost_per_pair to get more pairs (or closer to target_t_pairs).
// A higher cost_per_pair makes each pair less attractive (larger value when minimizing)
// which results in fewer pairs chosen.
// If pair_count <= target_t_pairs, it means `mid_cost` is too high (or just right)
// for achieving `target_t_pairs`. We want to make `mid_cost` smaller to increase `pair_count`.
// Wait, standard WQS is `if count > target, low = mid+1`. If count <= target, `high = mid-1`.
// If count <= target, it means we chose too few or exactly `target_t_pairs`.
// This means `mid_cost` is too *high* or just right. We want to decrease `mid_cost` to get more pairs, or keep it.
// So we record current_dp_result and try smaller costs.
optimal_cost_per_pair = mid_cost;
final_dp_state = current_dp_result; // Store the result for this mid_cost
high_cost = mid_cost - 1;
} else {
// We have too many pairs for this cost. Increase cost_per_pair.
low_cost = mid_cost + 1;
}
}
// Recalculate with optimal_cost_per_pair to get the exact pair count for that cost.
// This is important because multiple mid_cost values can yield the same pair_count.
DPState actual_final_dp = calculate_min_cost(m_cols, optimal_cost_per_pair);
// The final value is the actual_final_dp.total_value (after subtracting optimal_cost_per_pair for each chosen pair)
// PLUS optimal_cost_per_pair * target_t_pairs.
// This effectively "undoes" the WQS transformation and applies the target_t_pairs constraint.
long long result_F_XY = (actual_final_dp.total_value + optimal_cost_per_pair * target_t_pairs) % MOD;
if (result_F_XY < 0) result_F_XY += MOD; // Ensure positive result
long long final_answer = (result_F_XY * sum_a_values) % MOD;
std::cout << final_answer << std::endl;
return 0;
}