[NOI2010]航空管制

对于第一问,对于每条限制 \((u, v)\) 我们连边 \(v \rightarrow u\),这样将构成一张 \(\rm DAG\)。首先先拓扑排序,对于拓扑序考前的点我们先让他尽量往后放,显然这样是最优的,因为我们在偏序满足条件的情况下让每个点尽量后放以保证方案的合法性,但需要注意的一点是可能拓扑序大的点能放的位置比较靠后而拓扑序小的点能放的位置比较靠前,那么这样两个都尽量后放就不满足偏序关系了,因此我们在拓扑排序时需要先进行一遍 \(a_v = \min\{a_u, a_v\}\) 以保证后面的点能放的位置在 \(u\) 前。

上面那个找到某个位置左边第一个没有放东西的位置可以使用线段树来实现。具体地我们将当前每个选过的位置上标号为选它的点,没选的位置标号为 \(0\),那么我需要查询的实际上是某个位置往左第一个是 \(0\) 的位置,注意到所有权值都是非负的,那么如果一段区间内最小值为 \(0\) 那么就说明这段区间内肯定出现了 \(0\) 反之则没有出现 \(0\),于是我们可以使用线段树维护区间最小值,每个区间记录两个值 \(min, p\),分别为这个区间内的最小值和最右边的 \(0\) 的位置,没有 \(0\)\(p = 0\),显然这个东西是可以左右两个区间合并的。那么我们每次相当于只要查询 \(1 \sim x\) 这个前缀中最大的 \(p\) 就可以知道 \(x\) 左边第一个为 \(0\) 的位置了,当然这个方法还使用于查区间第一个为 \(0\) 的位置。同样这个方法也可以来做查询左边第一个比他小的位置。

再来考虑第二问,观察一下上面我们拓扑排序的过程,实际上是让一部分点尽量靠后,那么我们为了让 \(x\) 放得尽可能靠前,可以直接先不考虑 \(x\) 和他能遍历到的点,让别的点尽量靠后,再最后查询一下当前序列最后能放的位置就是 \(x\) 能放的最前位置。复杂度 \(O(n ^ 2 \log n + nm)\) 加了火车头才过的

#include<bits/stdc++.h>
using namespace std;
#define N 2000 + 5
#define M 10000 + 5
#define ls (p << 1)
#define rs (p << 1 | 1)
#define mid (l + r) / 2
#define rep(i, l, r) for(register int i = l; i <= r; ++i)
#define Next(i, u) for(register int i = h[u]; i; i = e[i].next)
struct tree{
    int min, p;
}t[N << 4];
struct edge{
    int v, next;
}e[M];
int n, m, u, v, tot, a[N], h[N], d[N], P[N], ans[N], Index[N];
queue <int> Q;
inline int read(){
    char c; int x = 0, f = 1;
    c = getchar();
    while(c > '9' || c < '0'){ if(c == '-') f = -1; c = getchar();}
    while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
    return x * f;
}
inline void add(int u, int v){
    e[++tot].v = v, e[tot].next = h[u], h[u] = tot;
}
inline void build(int p, int l, int r){
    t[p].min = 0, t[p].p = r;
    if(l == r) return;
    build(ls, l, mid), build(rs, mid + 1, r);
}
inline void update(int p, int l, int r, int x, int y, int k){
    if(l >= x && r <= y){ t[p].min = k, t[p].p = 0; return;}
    if(mid >= x) update(ls, l, mid, x, y, k);
    if(mid < y) update(rs, mid + 1, r, x, y, k);
    t[p].min = min(t[ls].min, t[rs].min);
    if(!t[rs].min) t[p].p = t[rs].p;
    else if(!t[ls].min) t[p].p = t[ls].p;
    else t[p].p = 0;
}
inline int query(int p, int l, int r, int x, int y){
    if(l >= x && r <= y) return t[p].p;
    int ans = 0;
    if(mid >= x) ans = max(ans, query(ls, l, mid, x, y));
    if(mid < y) ans = max(ans, query(rs, mid + 1, r, x, y));
    return ans;
}
int solve(int p){
    build(1, 1, n);
    rep(i, 1, n) Index[i] = d[i];
    rep(i, 1, n) if(!Index[i] && i != p) Q.push(i);
    while(!Q.empty()){
        int u = Q.front(); Q.pop();
        ans[u] = query(1, 1, n, 1, a[u]), update(1, 1, n, ans[u], ans[u], u);
        Next(i, u){
            int v = e[i].v; --Index[v];
            if(!Index[v] && v != p) Q.push(v);
        }
    }
    return query(1, 1, n, 1, a[p]);
}
int main(){
    n = read(), m = read();
    rep(i, 1, n) a[i] = read();
    rep(i, 1, m) u = read(), v = read(), add(v, u), ++d[u];
    rep(i, 1, n) Index[i] = d[i];
    rep(i, 1, n) if(!Index[i]) Q.push(i);
    build(1, 1, n);
    while(!Q.empty()){
        int u = Q.front(); Q.pop(); 
        ans[u] = query(1, 1, n, 1, a[u]), update(1, 1, n, ans[u], ans[u], u);
        Next(i, u){
            int v = e[i].v; --Index[v], a[v] = min(a[v], a[u]);
            if(!Index[v]) Q.push(v);
        }
    }
    rep(i, 1, n) P[ans[i]] = i;
    rep(i, 1, n) printf("%d ", P[i]); puts("");
    rep(i, 1, n) printf("%d ", solve(i));
    return 0;
}
posted @ 2020-08-12 23:09  Achtoria  阅读(213)  评论(0编辑  收藏  举报