Techniques for Solving Problems Based on Partial Order Relations

Many computational problems require determining answers based on partial order relationships between elements. When only the relative ordering matters, we can employ specialized enumeration strategies to sidestep complex case-by-case analysis. The main approaches include:

  • Comparison Operators (): Process elements sequentially from smallest to largest.
  • Absolute Value and Extremum Functions (abs, max, min): Transform these into < or > relationships, then apply the same enumeration technique.
  • Coordinate System Methods: For two-dimensional partial order problems, geometric interpretation in the coordinate plane often proves effective.

Below are two illustrative problems demonstrating these techniques:

[Contest Problem] Edge-Weight Minimization on Tree Paths

Problem Statement:

Given a tree with n (n ≤ 10⁶) nodes and weighted edges, define Min(x, y) as the minimum edge weight along the unique path from node x to node y. Compute:

max_{r = 1}^n {∑_{v ≠ r} Min(r, v)}

Solution Approach:

The key insight is that only the smallest edge on each path matters. We can process edges in ascending order by weight, tracking how each edge contributes to the sum by splitting nodes into two connected components. Performing this forward would be inefficient, so we reverse the process: start with all edges removed and gradually add them back using a Union-Find data structure.

When merging two components via edge weight w, if they sizes are a and b, the contribution to the answer becomes max(val_a + b·w, val_b + a·w), where val represents the accumulated sum for each component.

Implementation

#include <bits/stdc++.h>
#pragma GCC optimize(3, "Ofast", "inline")
#define int long long
using namespace std;

const int MAXN = 1e6 + 10;

int n, parent[MAXN], compSize[MAXN], result[MAXN];
struct EdgeData {
    int from, to, weight;
} edges[MAXN];

bool compareEdges(const EdgeData& e1, const EdgeData& e2) {
    return e1.weight > e2.weight;
}

int getParent(int x) {
    return parent[x] = (parent[x] == x) ? x : getParent(parent[x]);
}

void uniteComponents(int x, int y, int weight) {
    int rootX = getParent(x);
    int rootY = getParent(y);
    if (rootX == rootY) return;
    
    if (compSize[rootX] < compSize[rootY]) swap(rootX, rootY);
    
    result[rootX] = max(result[rootX] + compSize[rootY] * weight, 
                        result[rootY] + compSize[rootX] * weight);
    parent[rootY] = rootX;
    compSize[rootX] += compSize[rootY];
}

signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    
    cin >> n;
    for (int i = 1; i <= n; i++) {
        compSize[i] = 1;
        parent[i] = i;
    }
    
    for (int i = 1; i < n; i++) {
        cin >> edges[i].from >> edges[i].to >> edges[i].weight;
    }
    
    sort(edges + 1, edges + n, compareEdges);
    
    for (int i = 1; i < n; i++) {
        uniteComponents(edges[i].from, edges[i].to, edges[i].weight);
    }
    
    cout << result[getParent(1)];
    return 0;
}

[Contest Problem] Two-Player Optimization Game

Problem Statement:

Alice and Bob play a game on a directed graph. A token starts at some node. Players alternate moves, with Alice moving first. On each turn, the current player moves the token along an outgoing edge. If no outgoing edges exist, the game ends immediately. The game terminates after 10¹⁰⁰ moves. Each node has a score equal to its index (starting from 1). Alice's final score equals the maximum node score visited during the game. Alice aims to maximize this score, while Bob aims to minimize it. Determine Alice's optimal score when starting from each node.

Solution Approach:

Define f_{u, 1/0} as the resulting score when Alice/Bob makes the move from node u. The optimal play equations are:

[\begin{aligned} f_{u, 0} &= \max(u, \max(f_{v, 1} | \exists (u, v))) \ f_{u, 1} &= \max(u, \min(f_{v, 0} | \exists (u, v))) \end{aligned} ]

Since the recurrence involves max and min operations (partial order relations), we can process nodes in descending order by index, progressively determining each state's answer.

When reaching node u during enumeration, if f_{u, 0/1} hasn't been determined yet, then f_{u, 0/1} = u by definition. We add newly determined answers to an update queue. For state (u, id):

  • id = 0 (Alice's turn): We update f_{v, 1} for successors v. During descending enumeration, the final update determines the actual value of f_{v, 1}. By tracking dynamic in-degrees, when a node v's in-degree reaches zero, we can set f_{v, 1} = f_{u, 0} and add it to the queue.
  • id = 1 (Bob's turn): We update f_{v, 0} for successors v. If f_{v, 0} hasn't been updated yet when processing, then f_{u, 1} becomes the minimum over all successors, establishing f_{v, 0}.

A standard BFS-style queue handles all update efficiently.

Implementation

#include <bits/stdc++.h>
#define NodePair pair<int, int>
#pragma GCC optimize(3, "Ofast", "inline")
using namespace std;

const int MAXN = 1e5 + 10;

int n, m, incoming[MAXN], dp[MAXN][2];

struct EdgeNode {
    int dest, nextEdge;
} edges[MAXN << 1];

int edgeHead[MAXN], edgeCount;

void addDirectedEdge(int source, int dest) {
    edges[++edgeCount] = {dest, edgeHead[source]};
    edgeHead[source] = edgeCount;
}

void computeScores(int startNode) {
    queue<NodePair> q;
    if (!dp[startNode][0]) {
        dp[startNode][0] = startNode;
        q.emplace(startNode, 0);
    }
    if (!dp[startNode][1]) {
        dp[startNode][1] = startNode;
        q.emplace(startNode, 1);
    }
    
    while (!q.empty()) {
        auto [current, player] = q.front();
        q.pop();
        
        for (int i = edgeHead[current]; i; i = edges[i].nextEdge) {
            int neighbor = edges[i].dest;
            
            if (player == 0 && !dp[neighbor][1]) {
                dp[neighbor][1] = startNode;
                q.emplace(neighbor, 1);
            } else if (player == 1) {
                incoming[neighbor]--;
                if (!incoming[neighbor]) {
                    dp[neighbor][0] = startNode;
                    q.emplace(neighbor, 0);
                }
            }
        }
    }
}

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    
    cin >> n >> m;
    for (int i = 0; i < m; i++) {
        int u, v;
        cin >> u >> v;
        addDirectedEdge(v, u);
        incoming[u]++;
    }
    
    for (int i = n; i > 0; i--) {
        computeScores(i);
    }
    
    for (int i = 1; i <= n; i++) {
        cout << dp[i][1] << " ";
    }
    
    return 0;
}

Tags: Union-Find tree-algorithms graph-theory topological-order partial-order

Posted on Sat, 05 Sep 2026 16:26:53 +0000 by OldWolf