P4434 [COCI2017-2018#2] ​​Usmjeri

知识点:lca,种类并查集

新生赛原题。

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

简述

给定一棵 n 个节点的树,初始时每条边方向不确定,同时给定 m 组约束,第 i 组约束为 (ai,bi) 的形式,描述了一条树上路径 aibi。要求给每条边定向,使得所有的路径 aibi 中的边方向均相同,求可行的方案数,答案对 109+7 取模。
1n,m3×105
2S,256MB。

分析

有趣的做法,学到许多。

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

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

考虑使用种类并查集维护这个过程。我们设每条边的编号为这条边连接的深度较深的节点的编号,则编号范围为 2n,它们对应并查集 2n。另外建立 n1 个虚并查集,代表每条边对应的种类相反的边,编号为 2+n2×n。处理约束时,对于将路径 ailca(ai,bi) 和路径 bilca(ai,bi)相邻的边 xy,我们合并并查集 x,y 和并查集 x+n,y+n;当 lca(ai,bi)ailca(ai,bi)bi 时,我们合并并查集 ai,bi+n 和并查集 ai+n,bi。经过上述过程后统计这 2×(n1) 个并查集中合并后的的集合的数量 num2 。由于实并查集和虚并查集构成的集合是对称的,则在一开始的分析中所需的集合的数量 num1=num2,最终的方案数即为 2num22mod109+7

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

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

代码

复制复制
//知识点: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 @   Luckyblock  阅读(62)  评论(3编辑  收藏  举报
相关博文:
阅读排行:
· 周边上新:园子的第一款马克杯温暖上架
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?
· 使用C#创建一个MCP客户端
点击右上角即可分享
微信分享提示