P4434 [COCI2017-2018#2] Usmjeri
知识点:lca,种类并查集
新生赛原题。
什么嘛,我还是长了一点手的嘛
简述
给定一棵 个节点的树,初始时每条边方向不确定,同时给定 组约束,第 组约束为 的形式,描述了一条树上路径 。要求给每条边定向,使得所有的路径 中的边方向均相同,求可行的方案数,答案对 取模。
。
2S,256MB。
分析
有趣的做法,学到许多。
以 1 为根考虑,边的方向只有向上和向下两种,则每组约束实际上规定了边的朝向的种类关系。对于约束 ,记节点 的最近公共祖先为 ,则路径 上的所有边应属于同一种类、路径 上的所有边也属于同一种类;且当 时,路径 上的边和 上的边属于不同种类。按照上述方案依次处理所有边的种类数时若出现矛盾则无解。
经过上述处理我们可以将所有边分为数个集合,同一集合内的边属于同种朝向或相反朝向,当确定其中一条边的朝向时,其余所有边的朝向均可确定。则对于同一种类内部的所有边,这些边的朝向有向上和向下两种取值。记这样的集合数量为 ,最终的方案数即为 。
考虑使用种类并查集维护这个过程。我们设每条边的编号为这条边连接的深度较深的节点的编号,则编号范围为 ,它们对应并查集 。另外建立 个虚并查集,代表每条边对应的种类相反的边,编号为 。处理约束时,对于将路径 和路径 上相邻的边 和 ,我们合并并查集 和并查集 ;当 时,我们合并并查集 和并查集 。经过上述过程后统计这 个并查集中合并后的的集合的数量 。由于实并查集和虚并查集构成的集合是对称的,则在一开始的分析中所需的集合的数量 ,最终的方案数即为 。
进行链上相临的边合并时,暴力跳链复杂度过高,考虑对每条边都维护一个值 ,表示以这条边为最底端的链,向上合并到的深度最浅的边的编号,可以另外使用一个并查集进行维护。跳链时依靠 进行优化和路径压缩即可。
优化后跳链的总复杂度变为 级别,则复杂度瓶颈是求 ,代码总复杂度 级别。
代码
复制复制//知识点: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; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 周边上新:园子的第一款马克杯温暖上架
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?
· 使用C#创建一个MCP客户端