G. Counting Graphs Codeforces Round 891 (Div. 3) 1857G

Problem - G - Codeforces

题目大意:给出一棵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,每一条边提供的贡献也就是(s-w[i]]+1)^{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;
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

timidcatt

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值