树的每次的添加边操作可以抽象为两个集合的连接
并查集需维护集合合并的过程,需要按树的深度进行合并
合并两个集合等同于连接集合的根节点,每次合并,答案乘以的逆元(n和m分别代表两个集合的大小)
需要注意的是一个集合的根节点的父亲是否在另一个集合中存在,若不存在则输出0,因此需要维护一个数组表示该结点的父亲,
这题卡log,O(nlogn)的启发式合并可能过不了
下列是O(n)做法
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
constexpr int MOD = 998244353;
const int N = 1e6 + 5;
int fa[N] , faa[N] , dep[N];
ll cnt[N];
int find(int x) {
return (x == fa[x] ? x : (fa[x] = find(fa[x])));
}
void merge(int x , int y) {
x = find(x) , y = find(y);
if(x == y) return;
if(dep[x] < dep[y]) {
swap(x , y);
}
cnt[y] += cnt[x];
fa[x] = y;
}
ll power(ll x , ll y = MOD - 2) {
ll ans = 1;
while(y) {
if(y & 1) ans = ans * x % MOD;
x = x * x % MOD;
y >>= 1;
}
return ans;
}
vector<int> G[N];
void dfs(int now , int fa , int d) {
dep[now] = d;
for(auto it : G[now]) {
if(it == fa) continue;
faa[it] = now;
dfs(it , now, d + 1);
}
}
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
faa[1] = 1;
for(int i = 1 ; i < N ; ++i) fa[i] = i , cnt[i] = 1;
int n;
cin >> n;
vector<pair<int , int> > op(n - 1);
for(auto &[x , y] : op) cin >> x >> y;
for(int i = 1 ; i <= n - 1 ; ++i) {
int x , y;
cin >> x >> y;
G[x].emplace_back(y);
G[y].emplace_back(x);
}
dfs(1 , -1 , 1);
ll ans = 1;
for(auto [x , y] : op) {
x = find(x) , y = find(y);
if(find(faa[x]) != y && find(faa[y]) != x) {
cout << 0 << '\n';
return 0;
}
ans *= (cnt[x] * cnt[y]) % MOD;
ans %= MOD;
merge(x , y);
}
cout << power(ans) << '\n';
}