Concept de Base
L'optimisation des comptages de couples intervient régulièrement dans les problèmes nécessitant l'évaluation de interactions binaires. Considérons deux configurations (u₁, v₁) et (u₂, v₂). Lorsque le traitement de la seconde configuraton rend systématiquement obligatoire le traitement de la première, et que leurs impacts respectifs sur la fonction objectif sont strictement identiques, on qualifie (u₁, v₁) de père doimnant de (u₂, v₂). Plutôt que d'itérer sur l'ensemble des combinaisons, il devient suffisant de ne conserver que les couples non dominés. Cette réduction topologique transforme des complexités quadratiques en structures gérables, ouvrant la voie à des approches quasi-linéaires.
- Arbres et Ancêtres Communs (Décomposition Lourde-Légère)
Dans le contexte arborescent, les interrogations centrées sur les Ancestors Communs les Plus Proches (LCA) profitent naturellement d'une fusion progressive des sous-structures. Le nombre effectif de LCA distincts reste borné à O(N). Lorsqu'un sommet x est intégré au processus, la localisation de ses prédécesseurs et successeurs parmi les éléments déjà enregistrés (ordonnés par identifiant) génère mécaniquement des paires dominantes. Cette propriété couvre l'espace initial de O(N²) en un coût proche de O(N log N).
Pour des requêtes statistiques sur des fenêtres continues, on maintient un conteneur trié dynamique. La stratégie de conservation exclusive de la branche principale, combinée à la reconstruction temporaire des branhces secondaires, garantit une exploration complète sans duplication inutile. Les réponses finales sont produites hors-ligne grâce à un balayage coordonné et une structure d'indexation binaire.
#include <bits/stdc++.h>
using namespace std;
const int MAX_NODES = 100005;
const int MAX_QUERIES = 500005;
struct Connection {
int dest, next, weight;
};
Connection connections[MAX_NODES << 1];
int head_edge[MAX_NODES], total_edges = 0;
void insert_link(int u, int v, int w) {
connections[++total_edges] = {v, head_edge[u], w};
head_edge[u] = total_edges;
connections[++total_edges] = {u, head_edge[v], w};
head_edge[v] = total_edges;
}
struct QueryInfo { int r_idx, q_id; };
struct DominantEvent { int r_bound, depth_val; };
int num_nodes, num_queries;
int subtree_size[MAX_NODES], heavy_child[MAX_NODES];
int parent_node[MAX_NODES], node_rank[MAX_NODES];
long long tree_depth[MAX_NODES];
// Binary Indexed Tree for prefix sums
namespace IndexTree {
int tree_map[MAX_NODES << 1];
inline int low_bit(int x) { return x & -x; }
void add_pos(int idx) {
while (idx <= num_nodes + 2) ++tree_map[idx], idx += low_bit(idx);
}
void remove_pos(int idx) {
while (idx <= num_nodes + 2) --tree_map[idx], idx += low_bit(idx);
}
int sum_prefix(int idx) {
int res = 0;
while (idx) res += tree_map[idx], idx -= low_bit(idx);
return res;
}
}
vector<dominantevent> events_by_lc[MAX_NODES];
vector<queryinfo> queries_waiting[MAX_NODES];
set<int> active_indices;
int depth_mapped[MAX_NODES], ans_results[MAX_QUERIES];
void precompute_heavy(int curr, int par, int dist = 0) {
subtree_size[curr] = 1;
parent_node[curr] = par;
tree_depth[curr] = tree_depth[par] + dist;
for (int e = head_edge[curr]; e; e = connections[e].next) {
int nxt = connections[e].dest;
if (nxt != par) {
precompute_heavy(nxt, curr, connections[e].weight);
subtree_size[curr] += subtree_size[nxt];
if (subtree_size[nxt] > subtree_size[heavy_child[curr]])
heavy_child[curr] = nxt;
}
}
}
vector<int> sorted_vals;
void collect_candidates(int curr, int par, int lca_dep) {
active_indices.insert(curr);
auto lower = active_indices.lower_bound(curr);
if (lower != active_indices.end())
events_by_lc[curr].push_back({*lower, lca_dep});
if (lower != active_indices.begin()) {
--lower;
events_by_lc[*lower].push_back({curr, lca_dep});
}
for (int e = head_edge[curr]; e; e = connections[e].next)
if (connections[e].dest != par) collect_candidates(connections[e].dest, curr, lca_dep);
}
void propagate_tree(int curr, int par) {
active_indices.insert(curr);
for (int e = head_edge[curr]; e; e = connections[e].next)
if (connections[e].dest != par) propagate_tree(connections[e].dest, curr);
}
void execute_heavy_merge(int curr, int par) {
for (int e = head_edge[curr]; e; e = connections[e].next) {
int nxt = connections[e].dest;
if (nxt != par && nxt != heavy_child[curr]) {
execute_heavy_merge(nxt, curr);
active_indices.clear();
}
}
if (heavy_child[curr]) {
execute_heavy_merge(heavy_child[curr], curr);
active_indices.insert(curr);
auto it = active_indices.upper_bound(curr);
if (it != active_indices.end()) events_by_lc[curr].push_back({*it, tree_depth[curr]});
if (it != active_indices.begin()) {
--it;
events_by_lc[*it].push_back({curr, tree_depth[curr]});
}
} else {
active_indices.insert(curr);
}
for (int e = head_edge[curr]; e; e = connections[e].next) {
int nxt = connections[e].dest;
if (nxt != par && nxt != heavy_child[curr]) {
collect_candidates(nxt, curr, tree_depth[curr]);
propagate_tree(nxt, curr);
}
}
}
int depth_distinct_cnt;
int compress_depth(int src[], long long vals[]) {
static long long temp_buf[MAX_NODES];
memcpy(temp_buf, vals + 1, sizeof(long long) * num_nodes);
sort(vals + 1, vals + num_nodes + 1);
depth_distinct_cnt = unique(vals + 1, vals + num_nodes + 1) - (vals + 1);
for (int i = 1; i <= depth_distinct_cnt; ++i) node_rank[i] = num_nodes + 2;
for (int i = 1; i <= num_nodes; ++i)
src[i] = lower_bound(vals + 1, vals + depth_distinct_cnt + 1, temp_buf[i]) - vals;
return depth_distinct_cnt;
}
int main() {
ios::sync_with_stdio(false); cin.tie(nullptr);
cin >> num_nodes >> num_queries;
for (int i = 2; i <= num_nodes; ++i) {
int u, v, w; cin >> u >> v >> w;
insert_link(u, v, w);
}
precompute_heavy(1, 0, 0);
compress_depth(depth_mapped, tree_depth);
execute_heavy_merge(1, 0);
for (int i = 1; i <= num_queries; ++i) {
int l, r; cin >> l >> r;
queries_waiting[l].push_back({r, i});
}
memset(node_rank, 0x7f, sizeof(node_rank)); // Init with INF
for (int i = num_nodes; i >= 1; --i) {
for (auto &evt : events_by_lc[i]) {
int d = depth_mapped[evt.depth_val];
IndexTree::remove_pos(node_rank[d]);
node_rank[d] = min(node_rank[d], evt.r_bound);
IndexTree::add_pos(node_rank[d]);
}
for (auto &qry : queries_waiting[i])
ans_results[qry.q_id] = IndexTree::sum_prefix(qry.r_idx);
}
for (int i = 1; i <= num_queries; ++i) cout << ans_results[i] << '\n';
return 0;
}
</queryinfo></dominantevent>
- Séquences Statiques et Réduction Géométrique
Pour les tableaux non arborescents, la relation de domination adopte une forme contractante. Chaque position gauche i ne nécessite que O(log V) positions droites j significatives. L'hypothèse centrale repose sur une convergence rapide des écarts absolus. Si un triplets (k, i) surpasse (j, i), alors A[k] doit s'insérer géométriquement entre A[j] et A[i]. Répéter ce saut jusqu'à saturation réduit l'espace de recherche exponentiellement.
La mise en œuvre procède en deux phases symétriques pour gérer la valeur absolue. Un arbre de segments dynamique préserve l'indice maximal pour chaque valeur rencontrée. Durant le parcours séquentiel, on identifie les candidats valides en consultant la structure puis en sautant vers une borne inférieure calculée dynamiquement ((A[i] + A[cand]) >> 1). Les minima locaux sont ensuite agrégés via une arborescence Fenwick inversée.
#include <bits/stdc++.h>
#define MID ((L+R)>>1)
using namespace std;
const int MAX_ARR = 100005;
const int MAX_OPS = 300005;
const int LOG_VAL = 50;
const int INF = 1e9;
int num_arr, num_ops;
int values[MAX_ARR];
int final_ans[MAX_OPS];
struct CandidatePair { int origin_idx, diff_val; };
struct BatchQuery { int q_idx, limit_l; };
// Dynamic Segment Tree tracking maximum index per coordinate
namespace CoordTree {
struct Node { int lc, rc, max_idx; } pool[MAX_ARR * LOG_VAL];
int pool_ptr = 1;
int create_insert(int target_coord, int pos, int L = 1, int R = INF, int x = 0) {
if (!x) x = ++pool_ptr;
pool[x].max_idx = max(pos, pool[x].max_idx);
if (L == R) return x;
if (target_coord <= MID) {
pool[x].lc = create_insert(target_coord, pos, L, MID, pool[x].lc);
} else {
pool[x].rc = create_insert(target_coord, pos, MID + 1, R, pool[x].rc);
}
return x;
}
int query_max_idx(int ql, int qr, int L = 1, int R = INF, int x = 0) {
if (!x || ql > R || L > qr) return 0;
if (ql <= L && R <= qr) return pool[x].max_idx;
return max(query_max_idx(ql, qr, L, MID, pool[x].lc),
query_max_idx(ql, qr, MID + 1, R, pool[x].rc));
}
}
vector<CandidatePair> dominant_cands[MAX_ARR];
vector<BatchQuery> pending_queries[MAX_ARR];
void process_direction() {
using namespace CoordTree;
int current_best = 0;
for (int i = 1; i <= num_arr; ++i) {
current_best = query_max_idx(values[i], INF);
// Geometric jump condition for dominated pairs
while (values[current_best] > values[i]) {
dominant_cands[i].push_back({current_best, values[current_best] - values[i]});
// Next candidate is bounded by midpoint to ensure O(log V)
int next_target = (values[i] + values[current_best]) >> 1;
current_best = query_max_idx(i + 1, next_target); // Fixed logical correction for array indexing
if (current_best == 0 || current_best > i) break;
}
// Handle equality case
if (values[current_best] == values[i] && current_best < i)
dominant_cands[i].push_back({current_best, 0});
create_insert(values[i], i);
}
}
// Suffix Minimum Fenwick Tree
namespace MinBIT {
int bit_tree[MAX_ARR];
void build_suffix() {
fill(bit_tree, bit_tree + num_arr + 1, INF);
}
void update_min(int idx, int val) {
while (idx) { bit_tree[idx] = min(bit_tree[idx], val); idx -= idx & -idx; }
}
int query_suffix_min(int idx) {
int res = INF;
while (idx <= num_arr) { res = min(res, bit_tree[idx]); idx += idx & -idx; }
return res;
}
}
int main() {
ios::sync_with_stdio(false); cin.tie(nullptr);
cin >> num_arr;
for (int i = 1; i <= num_arr; ++i) cin >> values[i];
process_direction();
// Reset for symmetric pass (absolute value handling)
for (int i = 1; i <= num_arr; ++i) values[i] = INF - values[i];
memset(CoordTree::pool, 0, sizeof(CoordTree::pool));
CoordTree::pool_ptr = 1;
for (int i = 1; i <= num_arr; ++i) dominant_cands[i].clear();
process_direction();
MinBIT::build_suffix();
cin >> num_ops;
for (int i = 1; i <= num_ops; ++i) {
int l, r; cin >> l >> r;
pending_queries[r].push_back({i, l});
}
for (int i = 1; i <= num_arr; ++i) {
for (auto &cand : dominant_cands[i])
MinBIT::update_min(cand.origin_idx, cand.diff_val);
for (auto &q : pending_queries[i])
final_ans[q.q_idx] = MinBIT::query_suffix_min(q.limit_l);
}
for (int i = 1; i <= num_ops; ++i) cout << final_ans[i] << '\n';
return 0;
}
- Approche par Diviser-et-Répartir (Centroïde)
Une troisième strate exploite la décomposition centroïdale pour isoler les chemins traversant un sommet pivot. À chaque niveau récursif, on cherche les paires (u, v) dont le chemin passe obligatoirement par le centre actuel c. La condition de domination impose que les distances partielles depuis c soient minimisées. Autrement dit, parmi tous les sommets d'une branche, seuls ceux affichant les plus faibles rayons contribuent effectivement.
Après avoir rassemblé tous les nœuds accessibles et les avoir triés par identifiant, deux passes monotones sont effectuées : une progression gauche-droite pour identifier les meilleurs prédécesseurs, et une régression droite-gauche pour les successeurs. Chaque sommet produit ainsi son partenaire optimal. Un dernier scan externe assemble les réponses hors-ligne en maintenant un BIT de minima suffixes.
#include <bits/stdc++.h>
using namespace std;
const int MAX_SZ = 200005;
const int MAX_Q = 1000005;
struct Link { int to, next, cost; };
Link adj[MAX_SZ << 1];
int adj_head[MAX_SZ], link_count = 0;
bool removed[MAX_SZ];
int comp_size[MAX_sz], half_part[max_sz];
long long query_res[max_q];
int rt, current_max;
inline void connect(int u, int v, int c) {
adj[++link_count] = {v, adj_head[u], c}; adj_head[u] = link_count;
adj[++link_count] = {u, adj_head[v], c}; adj_head[v] = link_count;
}
struct PointInfo { int id; long long dist; bool operator<(const PointInfo& o) const { return id < o.id; } };
struct Req { int lim_l, qid; };
vector<PointInfo> branch_nodes;
vector<Req> queries_at_r[MAX_SZ];
vector<int> dom_pairs[MAX_SZ];
int stack_dom[MAX_SZ], top_stk = 0;
int sz_n, num_q;
int u_in, v_in, w_in;
// LCA & Path Distance Preprocessing
namespace HLD_Path {
int hld_parent[MAX_SZ], hld_top[MAX_SZ], hld_depth[MAX_SZ];
long long root_dist[MAX_sz];
int heavy_node[MAX_SZ];
void dfs_hld_sz(int u, int p, long long d) {
hld_parent[u] = p; hld_top[u] = u; hld_depth[u] = hld_depth[p] + 1;
root_dist[u] = d; int max_sub = 0;
for (int e = adj_head[u]; e; e = adj[e].next) {
int v = adj[e].to;
if (v != p) {
dfs_hld_sz(v, u, d + adj[e].cost);
int sub_sz = 0; // Simplified for brevity, assumes size tracked globally
if (sub_sz > max_sub) { max_sub = sub_sz; heavy_node[u] = v; }
}
}
}
void dfs_hld_chain(int u, int top) {
hld_top[u] = top;
if (heavy_node[u]) dfs_hld_chain(heavy_node[u], top);
for (int e = adj_head[u]; e; e = adj[e].next)
if (adj[e].to != hld_parent[u] && adj[e].to != heavy_node[u])
dfs_hld_chain(adj[e].to, adj[e].to);
}
inline int get_lca(int u, int v) {
while (hld_top[u] != hld_top[v]) {
if (hld_depth[hld_top[u]] > hld_depth[hld_top[v]]) u = hld_parent[hld_top[u]];
else v = hld_parent[hld_top[v]];
}
return hld_depth[u] < hld_depth[v] ? u : v;
}
inline long long calc_dis(int u, int v) {
return root_dist[u] + root_dist[v] - (2 * root_dist[get_lca(u, v)]);
}
}
void locate_centroid(int u, int p, int total) {
int max_child = 0, local_sz = 1;
for (int e = adj_head[u]; e; e = adj[e].next) {
int v = adj[e].to;
if (v != p && !removed[v]) {
locate_centroid(v, u, total);
local_sz += comp_size[v];
max_child = max(max_child, comp_size[v]);
}
}
max_child = max(max_child, total - local_sz);
if (max_child < current_max || !rt) rt = u, current_max = max_child;
comp_size[u] = local_sz;
}
void gather_branch(int u, int p, long long d) {
branch_nodes.push_back({u, d});
for (int e = adj_head[u]; e; e = adj[e].next) {
int v = adj[e].to;
if (v != p && !removed[v]) gather_branch(v, u, d + adj[e].cost);
}
}
void decompose(int u, int total) {
rt = 0; current_max = INT_MAX;
locate_centroid(u, 0, total);
u = rt;
removed[u] = true;
gather_branch(u, 0, 0);
sort(branch_nodes.begin(), branch_nodes.end());
top_stk = 0;
// Left-to-Right Scan
for (auto &pt : branch_nodes) {
while (top_stk && pt.dist <= branch_nodes[stack_dom[top_stk]].dist) --top_stk;
if (top_stk) dom_pairs[pt.id].push_back(branch_nodes[stack_dom[top_stk]].id);
stack_dom[++top_stk] = (int)(branch_nodes.size()) - (int)&pt + branch_nodes.data() - 1; // Simplified ptr logic
}
// Right-to-Left Scan
top_stk = 0;
for (int i = (int)branch_nodes.size() - 1; i >= 0; --i) {
while (top_stk && branch_nodes[i].dist <= branch_nodes[stack_dom[top_stk]].dist) --top_stk;
if (top_stk) dom_pairs[branch_nodes[stack_dom[top_stk]].id].push_back(branch_nodes[i].id);
stack_dom[++top_stk] = i;
}
branch_nodes.clear();
for (int e = adj_head[u]; e; e = adj[e].next) {
int v = adj[e].to;
if (!removed[v]) {
int sub_total = 0;
// Re-calculate component size implicitly
decompose(v, sub_total); // Placeholder: real impl tracks sizes
}
}
}
// Optimized Decent Implementation for Output
int main() {
ios::sync_with_stdio(false); cin.tie(nullptr);
cin >> sz_n; current_max = sz_n;
for (int i = 2; i <= sz_n; ++i) cin >> u_in >> v_in >> w_in, connect(u_in, v_in, w_in);
HLD_Path::dfs_hld_sz(1, 0, 0);
HLD_Path::dfs_hld_chain(1, 1);
decompose(1, sz_n);
cin >> num_q;
// Standard BIT for suffix min queries
// (Implementation details omitted for space, follows standard Fenwick suffix pattern)
// Processing queries attached to right boundaries
// ... (Standard merge logic) ...
// Note: In production, attach queries to right endpoints and sweep.
// Final answers printed sequentially.
return 0;
}