NOI模拟(5.8) HNOID2T1 游戏 (bzoj5288)

这篇博客分析了HNOI2018D2T1游戏问题,指出了解决方案的关键在于拓扑排序。作者在考场上采用暴力方法得分不高,但正解是通过判断门的左右状态,依据拓扑序处理能够到达的区间,并在过程中判断扩展可能性。由于每个节点只会从左右两侧扩展一次,总复杂度为O(n)。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

游戏

题目背景:

5.8 模拟 HNOI2018D2T1  

分析:拓扑排序

 

这道题好神啊我不会······考场上打了60分暴力,被卡成40,其他人不管是乱搞还是正解,还是什么奇奇怪怪的玄学都过了,感觉内心崩溃,直接说正解吧,考虑一扇门,如果钥匙在左边,那么显然只能从左边才可能到右边,并且,如果左边可以到右边那么右边的可以到的区间,左边的一定也可以到,那么我们应该先处理右边,然后之后在判断左边是否能到右边,那么我们就可以根据这个关系直接按照一个拓扑序来处理对应点能够到达的区间,然后每次判断能否扩展,就可以了,比较显然的是,一个节点显然只会从左边扩展一次,从右边扩展一次,被当做停止节点多次,但是显然停止节点总次数是O(n),所以,总复杂度是O(n)

 

Source:

 

/*
    created by scarlyw
*/
#include <cstdio>
#include <string>
#include <algorithm>
#include <cstring>
#include <iostream>
#include <cmath>
#include <cctype>
#include <vector>
#include <set>
#include <queue>
#include <ctime>
#include <bitset>
  
inline char read() {
    static const int IN_LEN = 1024 * 1024;
    static char buf[IN_LEN], *s, *t;
    if (s == t) {
        t = (s = buf) + fread(buf, 1, IN_LEN, stdin);
        if (s == t) return -1;
    }
    return *s++;
}
  
///*
template<class T>
inline void R(T &x) {
    static char c;
    static bool iosig;
    for (c = read(), iosig = false; !isdigit(c); c = read()) {
        if (c == -1) return ;
        if (c == '-') iosig = true; 
    }
    for (x = 0; isdigit(c); c = read()) 
        x = ((x << 2) + x << 1) + (c ^ '0');
    if (iosig) x = -x;
}
//*/
 
const int OUT_LEN = 1024 * 1024;
char obuf[OUT_LEN];
char *oh = obuf;
inline void write_char(char c) {
    if (oh == obuf + OUT_LEN) fwrite(obuf, 1, OUT_LEN, stdout), oh = obuf;
    *oh++ = c;
}
 
 
template<class T>
inline void W(T x) {
    static int buf[30], cnt;
    if (x == 0) write_char('0');
    else {
        if (x < 0) write_char('-'), x = -x;
        for (cnt = 0; x; x /= 10) buf[++cnt] = x % 10 + 48;
        while (cnt) write_char(buf[cnt--]);
    }
}
 
inline void flush() {
    fwrite(obuf, 1, oh - obuf, stdout), oh = obuf;
}
  
/*
template<class T>
inline void R(T &x) {
    static char c;
    static bool iosig;
    for (c = getchar(), iosig = false; !isdigit(c); c = getchar())
        if (c == '-') iosig = true; 
    for (x = 0; isdigit(c); c = getchar()) 
        x = ((x << 2) + x << 1) + (c ^ '0');
    if (iosig) x = -x;
}
//*/
 
inline void write(bool flag) {
    if (flag) write_char('Y'), write_char('E'), 
        write_char('S'), write_char('\n');
    else write_char('N'), write_char('O'), write_char('\n');
}
 
const int MAXN = 1000000 + 10;
 
int n, m, t, x, y, cnt;
int l[MAXN], r[MAXN], id[MAXN], pos[MAXN], p[MAXN], deg[MAXN];
bool left[MAXN], right[MAXN];
 
inline void solve() {
    R(n), R(m), R(t);
    for (int i = 1; i <= m; ++i) R(x), R(y), pos[x] = y;
    id[1] = cnt = 1;
    for (int i = 2; i <= n; ++i) id[i] = (!pos[i - 1]) ? cnt : ++cnt;
    cnt = 0;
    for (int i = 1; i <= n; ++i) {
        if (pos[i] != 0) {
            if (pos[i] > i) left[id[i + 1]] = true, deg[id[i + 1]]++;
            else right[id[i]] = true, deg[id[i]]++;
            p[++cnt] = id[pos[i]];
        }
    }
    cnt++;
    static int q[MAXN];
    int head = 0, tail = 0;
    for (int i = 1; i <= cnt; ++i) if (deg[i] == 0) q[++tail] = i;
    while (head < tail) {
        int cur = q[++head];
        if (cur != 1 && left[cur] == false) 
            if (--deg[cur - 1] == 0) q[++tail] = cur - 1;
        if (cur != cnt && right[cur] == false)
            if (--deg[cur + 1] == 0) q[++tail] = cur + 1;
    }
    for (int i = 1; i <= cnt; ++i) {
        int cur = q[i];
        bool flag = true;
        l[cur] = r[cur] = cur;
        while (flag) {
            flag = false;
            while (left[cur] && p[l[cur] - 1] >= l[cur] && 
                p[l[cur] - 1] <= r[cur]) l[cur] = l[l[cur] - 1], flag = true;
            while (right[cur] && p[r[cur]] >= l[cur] && 
                p[r[cur]] <= r[cur]) r[cur] = r[r[cur] + 1], flag = true;
        }
    }
    while (t--) R(x), R(y), x = id[x], y = id[y], write(y >= l[x] && y <= r[x]);
}
 
int main() {
    //freopen("in.in", "r", stdin);
    solve();
    flush();
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值