[BZOJ4383][POI2015]Pustynia(线段树优化建图+拓扑排序)

Address

https://www.lydsy.com/JudgeOnline/problem.php?id=4383

Solution

可以想到,如果对于 i,j i , j 要求满足 a[i]>a[j] a [ i ] > a [ j ] 的约束,就建边 <i,j> < i , j > <script type="math/tex" id="MathJax-Element-3"> </script> ,建图后拓扑排序,如果有环就无解。
但要解决两个问题:
(1)已经钦定的数值。
我们可以把第 i i 个数尽量填成能填的最大的数(如果入度为 0 且未被钦定就填 109 10 9 )。
这个可以在拓扑排序上 dp 得到。
(2)建图的复杂度问题。
考虑优化建图。由于 k ∑ k 不大,所以对每个限制建一个虚拟节点,把 k k 个点连到虚拟节点上,再把虚拟节点向 [l,r] 内除掉这 k k 个位置剩下的 O(k) 个区间内的点连边。
一个点向一个区间连边,可以使用线段树优化建图来实现。

Code

#include <cmath>
#include <cstdio>
#include <vector>
#include <cstring>
#include <iostream>
#include <algorithm>
#define For(i, a, b) for (i = a; i <= b; i++)
using namespace std;
inline int read() {
    int res = 0; bool bo = 0; char c;
    while (((c = getchar()) < '0' || c > '9') && c != '-');
    if (c == '-') bo = 1; else res = c - 48;
    while ((c = getchar()) >= '0' && c <= '9')
        res = (res << 3) + (res << 1) + (c - 48);
    return bo ? ~res + 1 : res;
}
const int N = 1e6 + 5, INF = 1e9;
int n, s, m, to[N], val[N], lc[N], rc[N], nm = 1, ps[N],
H, T, Q[N], cnt[N];
bool ch[N];
vector<int> eg[N], rp[N];
void add_edge(int u, int v, int w) {
    eg[u].push_back(v); rp[u].push_back(w); cnt[v]++;
}
void build(int l, int r, int p) {
    if (l == r) return (void) (to[l] = p);
    int mid = l + r >> 1;
    lc[p] = ++nm; build(l, mid, lc[p]);
    rc[p] = ++nm; build(mid + 1, r, rc[p]);
    add_edge(p, lc[p], 0); add_edge(p, rc[p], 0);
}
void linkedge(int u, int l, int r, int s, int e, int p) {
    if (l == s && r == e) return (void) (add_edge(u, p, 1));
    int mid = l + r >> 1;
    if (e <= mid) linkedge(u, l, mid, s, e, lc[p]);
    else if (s >= mid + 1) linkedge(u, mid + 1, r, s, e, rc[p]);
    else linkedge(u, l, mid, s, mid, lc[p]),
        linkedge(u, mid + 1, r, mid + 1, e, rc[p]);
}
int main() {
    int i, l, r, k;
    n = read(); s = read(); m = read();
    build(1, n, 1);
    while (s--) {
        l = read(); r = read();
        if (r < 1 || r > INF) return puts("NIE"), 0;
        val[to[l]] = r; ch[to[l]] = 1;
    }
    while (m--) {
        nm++;
        l = read(); r = read(); k = read();
        For (i, 1, k) ps[i] = read(),
            add_edge(to[ps[i]], nm, 0);
        if (l < ps[1]) linkedge(nm, 1, n, l, ps[1] - 1, 1);
        if (ps[k] < r) linkedge(nm, 1, n, ps[k] + 1, r, 1);
        For (i, 1, k - 1) if (ps[i] + 1 < ps[i + 1])
            linkedge(nm, 1, n, ps[i] + 1, ps[i + 1] - 1, 1);
    }
    For (i, 1, nm) if (!val[i]) val[i] = INF;
    For (i, 1, nm) if (!cnt[i]) Q[++T] = i;
    while (H < T) {
        int u = Q[++H], otz = eg[u].size();
        For (i, 0, otz - 1) {
            int v = eg[u][i], w = rp[u][i];
            if (!(--cnt[v])) Q[++T] = v;
            if (ch[v] && val[u] - w < val[v]) return puts("NIE"), 0;
            if (ch[v]) continue;
            val[v] = min(val[v], val[u] - w);
            if (val[v] < 1) return puts("NIE"), 0;
        }
    }
    if (T < nm) return puts("NIE"), 0;
    puts("TAK");
    For (i, 1, n) printf("%d ", val[to[i]]);
    cout << endl;
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值