题目大意:给出一棵n个点的边权树,问有多少个边权最大不超过s的图的最小生成树是这棵树
2<=n<=2e5;1<=w[i]<=1e9
思路:对于最小生成树上的每一条边,如果我们在包含这条边的链上的两点之间加了一条边权小于等于这条边的边权的边,最小生成树就可以走那条新边,同时我们也不能加点,所以对于一条边权为w[i]的边,我么只能在树上添加边权为w[i]+1~s的边,所以算上不加边,边权一共有s-w[i]+1种情况。
然后考察有多少位置可以加边,如果随机访问边的话,我们不知道当前加的边是否会影响其他的边,所以我们要从边权小的边开始遍历,加的边都应该在经过这条边的链上的两点之间,且边权大于当前边,这样就可以确保不影响其他的边。
因为我们已经算出了每个位置能加边的种类数,所以我们现在只要算出有多少个位置能加边即可,设我们当前遍访问的边的两个端点分别为u,v,我们要加的边应该一个端点在u的子树上,另一个在v的子树上,在我们当前确定的边权和位置条件下,u的子树上的每一个点都可以向v的子树上的每一个点建边,除了我们本身正在访问的边,总的位置数就是size[u]*size[v]-1,每一条边提供的贡献也就是。
我们可以用并查集维护当前访问过的边,在unite时连带维护每个点的子节点数量,对于每个边都考差它在当前连通出的图中能建多少图,这样加的边在我们的边权和位置限制条件下是不会影响其他边的,累加答案即可
#include<bits/stdc++.h>
//#include<__msvc_all_public_headers.hpp>
using namespace std;
typedef long long ll;
const int N = 2e5 + 10;
int n;
ll siz[N];
pair<ll,pair<int,int>> e[N];
int fa[N];
int lv[N];
const ll MOD = 998244353;
void init()
{
for (int i = 0; i <= n; i++)
{
siz[i] = 1;
fa[i] = i;
lv[i] = 0;
}
}
int find(int x)
{//并查集找祖先
return fa[x] == x ? x : find(fa[x]);
}
void unite(int a, int b)
{//并查集的合并
int x = find(a);
int y = find(b);
if (lv[x] < lv[y])//路径压缩
swap(x, y);
fa[y] = x;
siz[x] += siz[y];
if (lv[x] == lv[y])
{
lv[x]++;
}
}
ll qpow(ll a, ll b)
{//快速幂
ll ret = 1;
while (b)
{
if (b & 1)
{
ret = ret * a % MOD;
}
a = a * a % MOD;
b >>= 1;
}
return ret;
}
int main()
{
cin.tie(0);
cout.tie(0);
ios::sync_with_stdio(false);
int t;
cin >> t;
while (t--)
{
ll s;
cin >> n >> s;
init();
for (int i = 1; i < n; i++)
{
int u, v;
ll w;
cin >> u >> v >> w;
e[i] = {w, {u,v} };
}
sort(e + 1, e + n);
ll ans = 1;
for (int i = 1; i < n; i++)
{
int u = find(e[i].second.first);
int v = find(e[i].second.second);//当前边两端点所在的连通块
ans = ans * qpow(s - e[i].first + 1, siz[u] * siz[v] - 1)%MOD;//可选边权数(包括无这条边),两端点所在连通块大小相乘-1
unite(u, v);
}
cout << ans << endl;
}
return 0;
}