P4434 [COCI2017-2018#2] ​​Usmjeri

知识点:lca,种类并查集

新生赛原题。

什么嘛,我还是长了一点手的嘛

简述

给定一棵 \(n\) 个节点的树,初始时每条边方向不确定,同时给定 \(m\) 组约束,第 \(i\) 组约束为 \((a_i, b_i)\) 的形式,描述了一条树上路径 \(a_i\leftrightarrow b_i\)。要求给每条边定向,使得所有的路径 \(a_i \leftrightarrow b_i\) 中的边方向均相同,求可行的方案数,答案对 \(10^9 + 7\) 取模。
\(1\le n, m\le 3\times 10^5\)
2S,256MB。

分析

有趣的做法,学到许多。

以 1 为根考虑,边的方向只有向上和向下两种,则每组约束实际上规定了边的朝向的种类关系。对于约束 \((a_i, b_i)\),记节点 \(a_i, b_i\) 的最近公共祖先为 \(\operatorname{lca}(a_i, b_i)\),则路径 \(a_i\rightarrow \operatorname{lca}(a_i, b_i)\) 上的所有边应属于同一种类、路径 \(b_i\rightarrow \operatorname{lca}(a_i, b_i)\) 上的所有边也属于同一种类;且当 \(\operatorname{lca}(a_i, b_i)\not= a_i \land \operatorname{lca}(a_i, b_i)\not= b_i\) 时,路径 \(a_i\rightarrow \operatorname{lca}(a_i, b_i)\) 上的边和 \(b_i\rightarrow \operatorname{lca}(a_i, b_i)\) 上的边属于不同种类。按照上述方案依次处理所有边的种类数时若出现矛盾则无解。

经过上述处理我们可以将所有边分为数个集合,同一集合内的边属于同种朝向或相反朝向,当确定其中一条边的朝向时,其余所有边的朝向均可确定。则对于同一种类内部的所有边,这些边的朝向有向上和向下两种取值。记这样的集合数量为 \(\operatorname{num}_1\),最终的方案数即为 \(2^{\operatorname{num}_1}\)

考虑使用种类并查集维护这个过程。我们设每条边的编号为这条边连接的深度较深的节点的编号,则编号范围为 \(2\sim n\),它们对应并查集 \(2\sim n\)。另外建立 \(n-1\) 个虚并查集,代表每条边对应的种类相反的边,编号为 \(2+n\sim 2\times n\)。处理约束时,对于将路径 \(a_i\rightarrow \operatorname{lca}(a_i, b_i)\) 和路径 \(b_i\rightarrow \operatorname{lca}(a_i, b_i)\)相邻的边 \(x\)\(y\),我们合并并查集 \(x, y\) 和并查集 \(x + n, y + n\);当 \(\operatorname{lca}(a_i, b_i)\not= a_i \land \operatorname{lca}(a_i, b_i)\not= b_i\) 时,我们合并并查集 \(a_i, b_i+n\) 和并查集 \(a_i + n, b_i\)。经过上述过程后统计这 \(2\times (n-1)\) 个并查集中合并后的的集合的数量 \(\operatorname{num}_2\) 。由于实并查集和虚并查集构成的集合是对称的,则在一开始的分析中所需的集合的数量 \(\operatorname{num}_1 = \operatorname{num}_2\),最终的方案数即为 \(2^{\frac{\operatorname{num}_2}{2}}\bmod 10^9 + 7\)

进行链上相临的边合并时,暴力跳链复杂度过高,考虑对每条边都维护一个值 \(\operatorname{last}\),表示以这条边为最底端的链,向上合并到的深度最浅边的编号,可以另外使用一个并查集进行维护。跳链时依靠 \(\operatorname{last}\) 进行优化和路径压缩即可。

优化后跳链的总复杂度变为 \(O(n)\) 级别,则复杂度瓶颈是求 \(\operatorname{lca}\),代码总复杂度 \(O((n + m)\log n)\) 级别。

代码

//知识点:lca,种类并查集
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define LL long long
const int kN = 5e5 + 10;
const int kM = kN << 1;
const int p = 1e9 + 7;
//=============================================================
int n, m;
int edgenum, head[kN], v[kM], ne[kM];
int last[kN], f[kN << 2];
bool vis[kN << 1];
//=============================================================
inline int read() {
  int f = 1, w = 0; char ch = getchar();
  for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
  return f * w;
}
void Add(int u_, int v_) {
  v[++ edgenum] = v_;
  ne[edgenum] = head[u_];
  head[u_] = edgenum;
}
int Find(int x_) {
  return f[x_] == x_ ? x_ : f[x_] = Find(f[x_]);
}
void Merge(int x_, int y_) {
  int fx = Find(x_), fy = Find(y_);
  f[fx] = fy;
}
namespace Cut {
  const int kNode = kN;
  int fa[kNode], son[kNode], dep[kNode], sz[kNode], top[kNode];
  void Dfs1(int u_, int fa_) {
    fa[u_] = fa_;
    sz[u_] = 1;
    dep[u_] = dep[fa_] + 1;
    for (int i = head[u_]; i; i = ne[i]) {
      int v_ = v[i];
      if (v_ == fa_) continue ;
      Dfs1(v_, u_);
      if (sz[v_] > sz[son[u_]]) son[u_] = v_;
      sz[u_] += sz[v_];
    }
  }
  void Dfs2(int u_, int top_) {
    top[u_] = top_;
    if (son[u_]) Dfs2(son[u_], top_);
    for (int i = head[u_]; i; i = ne[i]) {
      int v_ = v[i];
      if (v_ == fa[u_] or v_ == son[u_]) continue ;
      Dfs2(v_, v_);
    }
  }
  int Lca(int u_, int v_) {
    for (; top[u_] != top[v_]; u_ = fa[top[u_]]) {
      if (dep[top[u_]] < dep[top[v_]]) {
        std::swap(u_, v_);
      }
    }
    return dep[u_] < dep[v_] ? u_ : v_;
  }
  int Findlast(int x_) {
    return last[x_] == x_ ? x_ : last[x_] = Findlast(last[x_]);
  }
  void MergeLast(int x_, int y_) {
    int lastx = Findlast(x_), lasty = Findlast(y_);
    last[lastx] = lasty;
  }
  void Dfs3(int u_, int end_) {
    u_ = Findlast(u_);
    int fau_ = Findlast(fa[u_]);
    if (dep[fa[u_]] <= dep[end_]) return ;
    Merge(u_, fau_), Merge(u_ + n, fau_ + n);
    MergeLast(u_, fau_);
    Dfs3(fau_, end_);
  }
}
int Qpow(int x_, int y_) {
  int ret = 1;
  while (y_) {
    if (y_ & 1) ret = 1ll * ret * x_ % p;
    y_ >>= 1, x_ = 1ll * x_ * x_ % p;
  }
  return ret;
}
//=============================================================
int main() {
  // freopen("1.txt", "r", stdin);
  n = read(), m = read();
  for (int i = 1; i < n; ++ i) {
    int u_ = read(), v_ = read();
    Add(u_, v_), Add(v_, u_);
  }
  Cut::Dfs1(1, 0), Cut::Dfs2(1, 1);
  
  for (int i = 1; i <= 2 * n; ++ i) f[i] = i;
  for (int i = 2; i <= n; ++ i) last[i] = i;
  while (m --) {
    int u_ = read(), v_ = read(), lca = Cut::Lca(u_, v_);
    if (lca == u_ || lca == v_) {
      if (lca == u_) std::swap(u_, v_);
      Cut::Dfs3(u_, v_);
    } else {
      Merge(u_, v_ + n), Merge(u_ + n, v_);
      Cut::Dfs3(u_, lca);
      Cut::Dfs3(v_, lca);
    }
  }

  int ans = 0;
  for (int i = 2; i <= 2 * n; ++ i) {
    if (i == n + 1) continue;
    int fai = Find(i);
    if (i <= n && Find(i) == Find(i + n)) {
      printf("0\n");
      return 0;
    }
    ans += !vis[fai];
    vis[fai] = 1;
  }
  printf("%d\n", Qpow(2, ans / 2));
  return 0;
}
posted @ 2023-03-09 22:24  Luckyblock  阅读(62)  评论(3编辑  收藏  举报