5288: [Hnoi2018]游戏
分析:
考虑y<=x的怎么做,那么只能从左边走到右边。我们可以从最右边的点开始,一次确定每个点往右边可以走多少。
L[x],R[x]分别是x向左向右最远走到的位置,初始L[x]=x,R[x]=x。R[n]=n,然后看n-1,如果n-1存在打开n-1这扇门的钥匙,那么说明n-1可以到n,相应的R[n-1]=R[n]。同样的考虑i,如果i可以打开第i扇门,那么R[i]=R[i+1],继续判断如果i可以打开第R[i]扇门,那么R[i]=R[R[i]]……
于是这样可以求出每个往右延伸的范围。复杂度的证明:每扇门只会被打开一次,每个点只会被求扫一次,于是复杂度是$O(n)$
如果y不小于x,考虑怎么做。将两个点之间的边确定方向(如果钥匙在左边,那么边是左->右,否则是右->左),然后对于找到一个点x,它既能向左走也可以向右走,从这个点开始,往左走到的是y,那么x->y这一段和y<=x的性质一样,做法也一样,扫一遍即可,往右走同理。最后处理x,直接暴力处理即可。
复杂度:每个点只会存在于一条链中,暴力处理的点总复杂度是O(n)的。
如果一条边上没有门呢?没法定向了,缩点即可。
代码:
#include<cstdio> #include<algorithm> #include<cstring> #include<cmath> #include<iostream> #include<cctype> #include<set> #include<vector> #include<queue> #include<map> #define fi(s) freopen(s,"r",stdin); #define fo(s) freopen(s,"w",stdout); using namespace std; typedef long long LL; inline int read() { int x=0,f=1;char ch=getchar();for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1; for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';return x*f; } const int N = 2000005; int a[N], b[N]; int n, m, Q; namespace BF1{ int L[2005], R[2005]; bool vis[2005]; vector<int> vec[2005], d; void Mark(int x) { for (int j = 0; j < (int)vec[x].size(); ++j) vis[vec[x][j]] = 1; } void Calc(int x) { L[x] = R[x] = x; for (int i = 0; i <= n; ++i) vis[i] = 0; for (int i = 0; i < (int)d.size(); ++i) vis[d[i]] = 1; Mark(x); while (1) { if (!vis[R[x]] && !vis[L[x] - 1]) break; if (vis[R[x]]) { R[x] ++; Mark(R[x]); } if (vis[L[x] - 1]) { L[x] --; Mark(L[x]); } } } void Main() { for (int i = 1; i <= m; ++i) vec[b[i]].push_back(a[i]); for (int i = 1; i <= m; ++i) vis[a[i]] = 1; for (int i = 1; i < n; ++i) if (!vis[i]) d.push_back(i); for (int i = 1; i <= n; ++i) Calc(i); while (Q--) { int s = read(), t = read(); if (L[s] <= t && t <= R[s]) puts("YES"); else puts("NO"); } } } int head[N], fa[N], vis[N], chu[N], nxt[N], pre[N], L[N], R[N], pos[N], En; vector<int> d; int find(int x) { return x == fa[x] ? x : fa[x] = find(fa[x]); } inline void Union(int x,int y) { if (x != y) fa[y] = x;} inline void add_edge(int x,int y) { x = find(x), y = find(y); chu[x] ++; } void Calc(int x) { int z = x; while (z >= 1 && chu[z] != 0) z = pre[z]; if (z < 1) z = nxt[z]; for (int i = z; i <= x; i = nxt[i]) { L[i] = i;R[i] = i; int now = pre[L[i]]; if (now < z) continue; while (L[i] <= pos[now] && pos[now] <= R[i]) { L[i] = L[now], now = pre[L[i]]; } } z = x; while (z <= n && chu[z] != 0) z = nxt[z]; if (z > n) z = pre[z]; for (int i = z; i >= x; i = pre[i]) { L[i] = i;R[i] = i; int now = R[i]; if (nxt[now] > z) continue; while (L[i] <= pos[now] && pos[now] <= R[i]) { R[i] = R[nxt[now]], now = R[i]; } } while (1) { bool f = 0; if (L[x] <= pos[R[x]] && pos[R[x]] <= R[x]) R[x] = nxt[R[x]], f = 1; if (L[x] <= pos[pre[L[x]]] && pos[pre[L[x]]] <= R[x]) L[x] = pre[L[x]], f = 1; if (!f) break; } } void solve() { for (int i = 1; i <= m; ++i) vis[a[i]] = 1; for (int i = 1; i <= n; ++i) fa[i] = i; for (int i = 1; i < n; ++i) if (!vis[i]) Union(i, i + 1); int last = 0; for (int i = 1; i <= n; ++i) if (find(i) == i) pre[i] = last, nxt[last] = i, last = i; nxt[last] = n + 1; pre[n + 1] = last; for (int i = 1; i <= m; ++i) { a[i] = find(a[i]), b[i] = find(b[i]); if (b[i] <= a[i]) add_edge(a[i], nxt[a[i]]); else add_edge(nxt[a[i]], a[i]); pos[a[i]] = b[i]; } for (int i = 1; i <= n; i = nxt[i]) { if (chu[i] == 2 || (i == nxt[0] && chu[i] == 1) || (i == pre[n + 1] && chu[i] == 1)) Calc(i); } } int main() { n = read(), m = read(), Q = read(); for (int i = 1; i <= m; ++i) a[i] = read(), b[i] = read(); if (n <= 1000 && m <= 1000) { BF1::Main(); return 0; } if (m == 0) { while (Q --) puts("YES"); return 0; } solve(); for (int i = 1; i <= n; ++i) if (!L[i]) L[i] = L[find(i)]; for (int i = n; i >= 1; --i) if (R[i]) R[i] = nxt[R[i]] - 1; for (int i = 1; i <= n; ++i) if (!R[i]) R[i] = R[find(i)]; while (Q--) { int s = read(), t = read(); if (L[s] <= t && t <= R[s]) puts("YES"); else puts("NO"); } return 0; }
代码: