[HNOI/AHOI2018]游戏

本文探讨了一种使用拓扑排序优化图论问题的方法,通过构建特定的图结构并进行拓扑排序,实现了对一系列点的影响区间的高效计算。这种方法在处理屋门和钥匙问题时表现出色,能够快速判断任意两点间是否可达,复杂度为O(n+m)。

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

1464677-20190424100457628-189777020.png


题解

一个点的影响区间显然是一段连续的区间
这样一个显然的\(O(n^2)\)暴力就是我们可以将一个点向左右扩展,处理出从这个点出发能到达的左右端点\(lp,rp\)
然后考虑怎么优化这个暴力
首先如果我们从点\(u\)向左扩展到\(l\),并且这个\(l\)已经被扩展过
那么我们就可以直接让\(lp[u]\)变成\(lp[l]\)
所以我们如果能确定一个顺序使得更新到这个点的时候所有ta能更新的点已经被更新过的话这个复杂度就变得肥肠优秀了
我们考虑每个门和钥匙\((x,Key_x)\)
如果钥匙在门的左边,那么显然门右边的点都过不了这个门
那么我们就对这个门\(x\)连一条\((x+1,x)\)
表示反正右边的点过不了门,所以先处理门右边的部分,再处理门左边的部分
反之就连\((x,x+1)\)
然后拓扑排序的时候当处理到这个点的时候ta能更新到的点都已经被更新了
所以直接暴力更新就可以了
然后注意如果两个屋子之间没有门就把他们缩起来
复杂度\(O(n+m)\)

代码

/*
Key[i] <= i 钥匙在左门在右
  add_edge(i+1,i)
Key[i] > i 钥匙在右门在左
  add_edge(i,i+1)

*/
#include<queue>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
const int M = 1000005 ;
using namespace std ;

inline int read() {
    char c = getchar() ; int x = 0 , w = 1 ;
    while(c>'9'||c<'0') { if(c=='-') w = -1 ; c = getchar() ; }
    while(c>='0'&&c<='9') { x = x*10+c-'0' ; c = getchar() ; }
    return x*w ;
}

bool exist[M] ;
int n , m , cnt , num ;
int f[M] , idx[M] , Key[M] ;
int d[M] , hea[M] , xp[M] , yp[M] , lp[M] , rp[M] ;
struct E {
    int nxt , to ;
} edge[M] ;
int find(int x) {
    if(f[x] != x)
        f[x] = find(f[x]) ;
    return f[x] ;
}
inline void add_edge(int from , int to) {
    edge[++num].nxt = hea[from] ;
    edge[num].to = to ;
    hea[from] = num ;
}

inline void topsort() {
    queue < int > q ;
    for(int i = 1 ; i <= cnt ; i ++) {
        lp[i] = rp[i] = i ;
        if(!d[i])
            q.push(i) ;
    }
    while(!q.empty()) {
        int u = q.front() ; q.pop() ;
        bool suc = true ;
        while(suc) {
            suc = false ;
            if(lp[u] > 1 && Key[lp[u] - 1] >= lp[u] && Key[lp[u] - 1] <= rp[u])
                lp[u] = lp[lp[u] - 1] , suc = true ;
            if(rp[u] < cnt && Key[rp[u]] <= rp[u] && Key[rp[u]] >= lp[u])
                rp[u] = rp[rp[u] + 1] , suc = true ;
        }
        for(int i = hea[u] ; i ; i = edge[i].nxt) {
            int v = edge[i].to ;
            -- d[v] ;
            if(!d[v])
                q.push(v) ;
        }
    }
}
int main() {
    n = read() ; m = read() ; int Case = read() ;
    for(int i = 1 ; i <= n ; i ++)
        f[i] = i ;
    for(int i = 1 ; i <= m ; i ++) {
        xp[i] = read() ; yp[i] = read() ;
        exist[xp[i]] = true ;
    }
    for(int i = 1 ; i < n ; i ++) {
        if(exist[i]) continue ;
        int x = find(i) , y = find(i + 1) ;
        if(x != y) f[y] = x ;
    }
    for(int i = 1 ; i <= n ; i ++) {
        if(find(i) != find(i - 1)) ++ cnt ;
        idx[i] = cnt ;
    }
    for(int i = 1 ; i <= m ; i ++)
        Key[idx[xp[i]]] = idx[yp[i]] ;
    for(int i = 1 ; i < cnt ; i ++) {
        if(Key[i] <= i) {
            add_edge(i + 1 , i) ;
            ++ d[i] ;
        }
        else {
            add_edge(i , i + 1) ;
            ++ d[i + 1] ;
        }
    }
    topsort() ;
    int x , y ;
    while(Case --) {
        x = read() ; y = read() ;
        if(idx[y] >= lp[idx[x]] && idx[y] <= rp[idx[x]])
            printf("YES\n") ;
        else printf("NO\n") ;
    }
    return 0 ;
}

转载于:https://www.cnblogs.com/beretty/p/10760731.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值