CF1679D

Solution

看到最大值最小,马上想到二分答案。

那就主要看 ${check}$ 函数怎么写,当二分答案为 $x$ 时,这里为了方便,先输入时把所有边存下来,然后每次二分重新建图,保证只有端点点权小于 $x$ 的。

在保证最大值一定小于 $x$ 后,我们要走 $k$ 步,可以直接找最长路,看一下有没有 $k$ 就可以了。

那么这里采用拓扑排序来动态规划,$f_i$ 表示从 $i$ 这个点出发能走多远,那么转移特别简单:$f_v=\max(f_v,f_u+1)$。

最后枚举找到大于 $k$ 的 $f_i$ 就好了。

Code

#include <bits/stdc++.h>
using namespace std;
using LL = long long;
const LL N = 2e5 + 10;
vector<vector<LL>> g(N);
LL n, m, a[N],k,ind[N], f[N];
pair<LL, LL> E[N]; 
void addEdge(LL u, LL v) {
    g[u].push_back(v);
}
bool check(LL x) {
    fill(g.begin(), g.end(), vector<LL>());
    memset(ind, 0, sizeof(ind));
    memset(f, 0, sizeof(f));
    for (LL i = 1; i <= m; i++) {
        if (a[E[i].first] <= x && a[E[i].second] <= x) {
            addEdge(E[i].first, E[i].second);
            ind[E[i].second]++;
        }
    }
    queue<LL> q;
    for (LL i = 1; i <= n; i++) {
        if (!ind[i] && a[i] <= x) {
            q.push(i);
            f[i] = 1;
        }
    }
    while (!q.empty()) {
        LL u = q.front();
        q.pop();
        for (LL v : g[u]) {
            f[v] = max(f[v], f[u] + 1);
            if (--ind[v] == 0) {
                q.push(v);
            }
        }
    }
    for (LL i = 1; i <= n; i++) {
        if (ind[i] || f[i] >= k) {
            return true;
        }
    }
    return false;
}
int main() {
    ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
    cin >> n >> m >> k;
    for (LL i = 1; i <= n; i++) {
        cin >> a[i];
    }
    for (LL i = 1; i <= m; i++) {
        cin >> E[i].first >> E[i].second;
    }
    LL l = 1, r = 1000000001, mid, ans = -1;
    while (l < r) {
        mid = (l + r) >> 1;
        if (check(mid)) {
            ans = mid;
            r = mid;
        } else {
            l = mid + 1;
        }
    }
    cout << ans << endl;
    return 0;
}