公式

I - タスクスケジュールと優先度フィルタ / Task Schedule and Priority Filter 解説 by admin

gemini-3-flash-thinking

Overview

For \(N\) tasks, \(M\) ordering constraints (requests) are added one by one in sequence. Each request has a priority assigned to it. By setting a “filter strength \(X\)” and ignoring requests with priority \(X\) or less, a schedule (topological order) that satisfies all remaining constraints can be created. For each step \(t\), the problem asks to find the minimum \(X_t\) that allows the constraints to be satisfied, and finally to output a valid schedule under \(X_M\).

Analysis

1. Constraint Satisfiability and Graphs

The ordering constraints between tasks can be modeled as a directed graph where tasks are vertices and constraints are directed edges. The condition “there exists a schedule that satisfies all constraints” is equivalent to the graph having no cycles (being a DAG: Directed Acyclic Graph).

2. Properties of Filter Strength \(X\)

As the filter strength \(X\) increases, more requests are ignored, which reduces the number of edges in the graph. Fewer edges make cycles less likely to occur, so the following monotonicity holds: - If the condition is satisfiable for some \(X\), then it is necessarily satisfiable for any larger \(X'\). - As time \(t\) progresses and more requests are added, the required filter strength \(X_t\) never decreases (\(X_t \leq X_{t+1}\)).

3. Candidates for \(X\)

The minimum \(X\) is always either \(0\) or one of the priorities \(W_j\) given in the input. By sorting the \(W_j\) values in advance and removing duplicates to create a list \(V\), it suffices to search for the optimal value within this list.

Algorithm

  1. Preparation:
    • Add \(0\) to the set of all \(W_j\) values, sort them, and create an array \(V\) of unique values.
  2. Determining \(X_t\) for each \(t\):
    • Utilize the monotonicity of \(X_t\). Maintain a pointer idx pointing to the current filter strength, and advance idx as needed as \(t\) increases.
    • Call the check_dag(t, V[idx]) function to build a graph using only the requests up to time \(t\) whose priority is greater than \(V[idx]\).
    • Continue incrementing idx while the graph has a cycle (is not a DAG).
    • Use Kahn’s algorithm (the in-degree based method) to determine whether the graph is a DAG.
  3. Outputting the Schedule:
    • Once \(X_M\) is determined, perform a topological sort again under that condition and output the resulting order.

Complexity

  • Time Complexity: \(O(M(N + \sum K_j))\)
    • In determining \(X_t\), the pointer idx advances at most \(M\) times in total.
    • The DAG check (Kahn’s algorithm) at each step runs in \(O(N + \sum K_j)\) for \(N\) vertices and \(\sum K_j\) edges.
    • Overall, this is \(O(M(N + \sum K_j))\), which runs sufficiently fast under the constraints (\(1000 \times (2000 + 3000) = 5 \times 10^6\)).
  • Space Complexity: \(O(N + \sum K_j)\)
    • This is the memory required for managing the adjacency list and in-degrees of the graph.

Implementation Notes

  • DAG Check: The standard approach is to enqueue vertices with in-degree \(0\), remove them one by one, and decrement the in-degrees of their neighbors. If the total number of removed vertices is less than \(N\), a cycle exists.

  • Optimization: Since the graph is rebuilt each time \(X_t\) is computed, operations such as clearing adjacency lists need to be performed efficiently.

  • Monotonicity of \(X_t\): By leveraging the fact that \(X_t\) is non-decreasing with respect to \(t\), the problem can be solved using a “two-pointer” style pointer management instead of binary search, which simplifies the implementation.

    Source Code

#include <iostream>
#include <vector>
#include <algorithm>
#include <set>
#include <queue>

using namespace std;

/**
 * Task Schedule and Priority Filter Problem
 * 
 * We are given N tasks and M sequential requests. Each request has a priority W_j 
 * and a sequence of tasks that must maintain their relative order.
 * For each step t=1 to M, we need to find the minimum filter strength X_t such that 
 * all requests j <= t with priority W_j > X_t can be satisfied simultaneously.
 * A set of constraints can be satisfied if and only if the directed graph 
 * representing those constraints is a Directed Acyclic Graph (DAG).
 * 
 * Time Complexity: O(M * (N + sum(K_j))), which is approximately 1000 * 5000 = 5 * 10^6.
 * This fits comfortably within the time limit.
 */

struct Request {
    int w;
    vector<int> p;
};

// Use fixed-size arrays for graph representation to improve performance
vector<int> adj[2005];
int in_deg[2005];

// Kahn's algorithm to check if the graph formed by requests j <= t with W_j > X is a DAG
bool check_dag(int t, int X, const vector<Request>& requests, int N) {
    for (int i = 1; i <= N; ++i) {
        adj[i].clear();
        in_deg[i] = 0;
    }
    for (int i = 0; i < t; ++i) {
        if (requests[i].w > X) {
            for (size_t k = 0; k + 1 < requests[i].p.size(); ++k) {
                adj[requests[i].p[k]].push_back(requests[i].p[k + 1]);
                in_deg[requests[i].p[k + 1]]++;
            }
        }
    }
    int count = 0;
    queue<int> q;
    for (int i = 1; i <= N; ++i) {
        if (in_deg[i] == 0) q.push(i);
    }
    while (!q.empty()) {
        int u = q.front();
        q.pop();
        count++;
        for (int v : adj[u]) {
            if (--in_deg[v] == 0) q.push(v);
        }
    }
    // If we visited all N nodes, it's a DAG
    return count == N;
}

// Kahn's algorithm to generate a topological sort (schedule)
vector<int> get_top_sort(int t, int X, const vector<Request>& requests, int N) {
    for (int i = 1; i <= N; ++i) {
        adj[i].clear();
        in_deg[i] = 0;
    }
    for (int i = 0; i < t; ++i) {
        if (requests[i].w > X) {
            for (size_t k = 0; k + 1 < requests[i].p.size(); ++k) {
                adj[requests[i].p[k]].push_back(requests[i].p[k + 1]);
                in_deg[requests[i].p[k + 1]]++;
            }
        }
    }
    vector<int> res;
    queue<int> q;
    for (int i = 1; i <= N; ++i) {
        if (in_deg[i] == 0) q.push(i);
    }
    while (!q.empty()) {
        int u = q.front();
        q.pop();
        res.push_back(u);
        for (int v : adj[u]) {
            if (--in_deg[v] == 0) q.push(v);
        }
    }
    return res;
}

int main() {
    // Fast I/O
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int N, M;
    if (!(cin >> N >> M)) return 0;

    vector<Request> requests(M);
    set<int> w_set;
    w_set.insert(0); // 0 is always a candidate for X
    for (int i = 0; i < M; ++i) {
        cin >> requests[i].w;
        int k;
        cin >> k;
        requests[i].p.resize(k);
        for (int j = 0; j < k; ++j) {
            cin >> requests[i].p[j];
        }
        w_set.insert(requests[i].w);
    }

    // Sorted unique priorities to search for X_t
    vector<int> V(w_set.begin(), w_set.end());
    vector<int> X_ans(M);
    int current_v_idx = 0;

    // X_t is non-decreasing as t increases
    for (int t = 1; t <= M; ++t) {
        while (current_v_idx < V.size() && !check_dag(t, V[current_v_idx], requests, N)) {
            current_v_idx++;
        }
        X_ans[t - 1] = V[current_v_idx];
    }

    // Output all X_t values
    for (int i = 0; i < M; ++i) {
        cout << X_ans[i] << (i == M - 1 ? "" : " ");
    }
    cout << "\n";

    // Find and output a valid schedule for X_M
    vector<int> schedule = get_top_sort(M, X_ans[M - 1], requests, N);
    for (int i = 0; i < N; ++i) {
        cout << schedule[i] << (i == N - 1 ? "" : " ");
    }
    cout << "\n";

    return 0;
}

This editorial was generated by gemini-3-flash-thinking.

投稿日時:
最終更新: