【IOI 2018】Werewolf 狼人

虽然作为IOI的Day1T3,但其实不是一道很难的题,或者说这道题其实比较套路吧。

接下来讲解一下这个题的做法:

 如果你做过NOI 2018的Day1T1,并且看懂了题面,那你很快就会联想到这道题,因为两者都是问某一个点能到达的点集,只不过限制在点上还是边上的问题。

$Kruskal$重构树可以把在边上的限制转化成点上的,于是能解决NOI 2018的Day1T1;那么这道题就可以直接做,因为限制已经在点上了。

具体来讲,从$s$点只能走编号$>=l$的点,那么我们就构建一棵树,使得任意一个非根节点的标号都比它父亲都大。要做到这一点,只要从大到小枚举点$x$,尝试把它加入树中,枚举所有与他相邻且编号比它大的节点$y$,如果$x$和$y$不在一个连通块里,就让$y$的并查集的根成为$x$的儿子,并在并查集里也连起来。

我们要对$s$和$t$都做一遍,也就是从大到小建一棵树$Tu$,从小到大建一棵树$Tv$。那么从$s$出发只走$>=l$的点能走到的点在$Tu$上就是一个子树;对$t$同理。

我们能用树上倍增找到$s$能走到的$Tu$上的那个子树,和$t$能走到的$Tv$的那个子树,这两者在两棵树上分别都对应$Dfs$序上的一段区间。

我们需要找到一个能变身的地点,它要满足能从$s$和$t$都能走到它,也就是在这两段$Dfs$上的区间中存在相同的元素。如何判断两个序列上的两个区间是否同时拥有某个元素,一般情况下我不会做,但是由于每个元素都恰好在一个序列中出现了一次,我们可以利用这个特殊的性质来解决。一个询问的答案是$1$当且仅当存在一个点,其中它在第一个序列中的位置在所求区间内,第二个序列也是。我们发现这就是一个简单的二维数点,我们把所有点在第一个和第二个$Dfs$序中中出现的位置分别当成$(x,y)$,一个询问就是判定一个二维平面上的一个矩形内是否有点。用离线树状数组实现比较方便。

 

$\bigodot$技巧&套路:

  •  点上限制和$Kruskal$重构树在边上的限制的联系
  • 两个$Dfs$序区间是否有相同元素向数点问题的转化

由于这道题本质是一道传统题,所以我用了传统的实现方法:

#include <cstdio>
#include <vector>
#include <algorithm>

using namespace std;

const int LOG = 21;
const int N = 200005;

int n, m, nq, qi;
int ans[N];
vector<int> g[N];

struct Que {
  int x, y, id, ty, f;
  friend bool operator < (const Que &a, const Que &b) {
    return (a.x != b.x)? (a.x < b.x) : (a.ty < b.ty);
  }
} q[N * 7];

struct Dsu {
  int fa[N];
  void Init(int n) {
    for (int i = 1; i <= n; ++i) fa[i] = i;
  }
  int Sk(int x) {
    return fa[x] == x? x : fa[x] = Sk(fa[x]);
  }
} U, V;

struct Tree {
  vector<int> g[N];
  int tot, din[N], dfn[N], row[N], ed[N], dep[N], gr[LOG][N];
  void Add(int x, int y) {
    g[x].push_back(y), ++din[y];
  }
  void Dfs(int x) {
    dfn[x] = ++tot, row[tot] = x;
    for (int i = 1; i < LOG; ++i)
      if (gr[i - 1][x]) gr[i][x] = gr[i - 1][gr[i - 1][x]];
    for (int v : g[x]) {
      gr[0][v] = x, dep[v] = dep[x] + 1;
      Dfs(v);
    }
    ed[x] = tot;
  }
  void Build() {
    int rt = -1;
    for (int i = 1; i <= n; ++i) if (din[i] == 0) rt = i;
    if (rt == -1) throw;
    Dfs(rt);
  }
} Tu, Tv;

int Fly_u(int x, int lim) {
  for (int i = LOG - 1; ~i; --i)
    if (Tu.gr[i][x] && Tu.gr[i][x] >= lim) x = Tu.gr[i][x];
  return x;
}
int Fly_v(int x, int lim) {
  for (int i = LOG - 1; ~i; --i)
    if (Tv.gr[i][x] && Tv.gr[i][x] <= lim) x = Tv.gr[i][x];
  return x;
}

namespace BIT {
  int t[N];
  void Add(int x) {
    for (; x <= n; x += x & -x) ++t[x];
  }
  int Qr(int x, int r = 0) {
    for (; x; x -= x & -x) r += t[x];
    return r;
  }
}


int main() {
  scanf("%d%d%d", &n, &m, &nq);
  for (int i = 1, x, y; i <= m; ++i) {
    scanf("%d%d", &x, &y);
    g[++x].push_back(++y), g[y].push_back(x);
  }
  U.Init(n), V.Init(n);
  for (int i = n; i >= 1; --i) {
    for (int j : g[i]) {
      if (j < i) continue;
      int y = U.Sk(j);
      if (i != y) U.fa[y] = i, Tu.Add(i, y);
    }
  }
  for (int i = 1; i <= n; ++i) {
    for (int j : g[i]) {
      if (j > i) continue;
      int y = V.Sk(j);
      if (i != y) V.fa[y] = i, Tv.Add(i, y);
    }
  }
  Tu.Build(), Tv.Build();
  for (int i = 1; i <= n; ++i) {
    q[i].x = Tu.dfn[i], q[i].y = Tv.dfn[i];
    q[i].ty = 1;
  }
  qi = n;
  for (int i = 1, x, y, l, r; i <= nq; ++i) {
    scanf("%d%d%d%d", &x, &y, &l, &r);
    ++x, ++y, ++l, ++r;
    int u = Fly_u(x, l), v = Fly_v(y, r);
    q[++qi] = (Que){ Tu.ed[u], Tv.ed[v], i, 2, 1 };
    if (Tu.dfn[u] > 1) q[++qi] = (Que){ Tu.dfn[u] - 1, Tv.ed[v], i, 2, -1 };
    if (Tv.dfn[v] > 1) q[++qi] = (Que){ Tu.ed[u], Tv.dfn[v] - 1, i, 2, -1 };
    if (Tu.dfn[u] > 1 && Tv.dfn[v] > 1) q[++qi] = (Que){ Tu.dfn[u] - 1, Tv.dfn[v] - 1, i, 2, 1 };
  }
  sort(q + 1, q + 1 + qi);
  for (int i = 1; i <= qi; ++i) {
    if (q[i].ty == 1) {
      BIT::Add(q[i].y);
    } else {
      ans[q[i].id] += q[i].f * BIT::Qr(q[i].y);
    }
  }
  for (int i = 1; i <= nq; ++i) {
    if (ans[i] < 0) throw;
    printf("%d\n", (bool)ans[i]);
  }

  return 0;
}
View Code

 

posted @ 2018-09-16 14:43  Dance_Of_Faith  阅读(937)  评论(1编辑  收藏  举报