知识点: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;
}
标签:P4434,COCI2017,top,查集,int,num,2018,lca,operatorname
From: https://www.cnblogs.com/luckyblock/p/17201740.html