G. Counting Graphs

G. Counting Graphs

Given a tree consisting of n vertices. A tree is a connected undirected graph without cycles. Each edge of the tree has its weight, wi.

Your task is to count the number of different graphs that satisfy all four conditions:

  1. The graph does not have self-loops and multiple edges.
  2. The weights on the edges of the graph are integers and do not exceed S.
  3. The graph has exactly one minimum spanning tree.
  4. The minimum spanning tree of the graph is the given tree.

Two graphs are considered different if their sets of edges are different, taking into account the weights of the edges.

The answer can be large, output it modulo 998244353.

Input

The first line contains an integer t (1t104) — the number of test cases.

The first line of each test case contains two integers n and S (2n2105,1S109) — the number of vertices and the upper bound of the weights.

Then follow n1 lines describing the tree, the i-th line contains three integers ui, vi, and wi (1ui,vin,uivi,1wiS) — an edge in the tree with weight wi.

It is guaranteed that the sum of n for all tests does not exceed 2105.

Output

For each test, output the number of different graphs that satisfy the conditions, modulo 998244353.

Example

input

复制代码
4
2 5
1 2 4
4 5
1 2 2
2 3 4
3 4 3
5 6
1 2 3
1 3 2
3 4 6
3 5 1
10 200
1 2 3
2 3 33
3 4 200
1 5 132
5 6 1
5 7 29
7 8 187
7 9 20
7 10 4
复制代码

output

1
8
80
650867886

Note

In the first sample, there is only one graph, which is the given tree.

In the second samle, the given tree looks like this:

All possible graphs for the second sample are shown below, the minimum spanning tree is highlighted in red:

 

解题思路

  问题就是让我们在原有的树中加一些额外的边,这些边不能是自环或重边,且边的权值不超过S,然后对构造的图求最小生成树,问有多少个这样的图所得到的最小生成树就是原来的树?

  先考虑加一条边,选择两个节点uv相连,当然uv不能直接相邻否则就会有重边。此时就会形成一个环,假设在uv相连前,从uv的简单路径中最大的权值为P(u,v),根据最小生成树的性质,这条新边的权值应该在[P(u,v)+1,S]这个区间内,这样构造的新图的最小生成树才是原来的那棵树。否则在这个环中就至少存在一条边的权值比新边大或相等,那么保留新边并选择一条权重比新边大或相等的边删去,得到一棵新树,可以知道这棵树的权重比原来的小或相等,最小生成树就肯定不是原来那棵树了。事实上只要新加的边满足上面的条件,那么构造的新图的最小生成树就一定是原来的树,因此加边及赋权值的行为可以看作是相互独立的。

  由于加的是无向边,为了避免重复这里规定从编号小的节点向编号大的节点连一条边,由于加边是独立的,结合乘法原理,那么满足要求的图的数量就是1u<vn(SP(u,v)+1),这其中考虑了某两个点不连边的情况,因此任意两个点间连边的情况就有SP(u,v)+1种(当然还要保证uv不直接相连)。可以发现如果直接暴力求的话时间复杂度至少是O(n2logn),这里不好直接优化(因为要双重循环枚举uv),因此尝试能不能用另外的方法求出这个值。

  可以发现任意两点uv连边后赋值的受限取决于原树中这两点简单路径上的最大权值P(u,v),在uv相连后,与原树形成的环就一定包含这条权值为P(u,v)的边,因此可以根据P(u,v)来对所有的新边进行分类,即根据形成的环所包含的最大权值的边进行分类。那么我们可以先对原树中的边按照权值从小到大排序,并且把原树中的所有边删除,然后再从小到大依次枚举每一条边并重新加入到树中。假设当前枚举到的第i条边(ui,vi,wi),若要以这条边作为环中最大权值的边,那么很明显要包含这条边,且其他边的权值不能超过wi。由于我们是从小到大往树中加边,那么我们就可以从与ui连通的点集,以及与vi连通的点集中分别选出一个点,然后这两个点相连形成新边(根据树的性质当枚举到第i条边时uivi不连通),这样新边所构成的环就一定包含了权值为wi的边,且其他边的权值不超过wi。假设与ui连通的点集大小为szui,与vi连通的点集大小为szvi,那么根据乘法原理在形成的环中最大权值为wi的新边数量就是szuiszvi1,减1是删去uivi这两个直接相连的点所构成的新边。又由于这szuiszvi1条新边中每条边都有Swi+1种选择,由独立性因此一共有(Swi+1)szuiszvi1种情况。

  可能你会问如果环中权值最大的边有多条怎么办?上面的做法会不会重复统计?答案是不会的,如果存在多条取值最大的边,那么我们就只认在排序后最靠前的边作为唯一的权值最大的边,当枚举完这条边后,这条边两个端点所在的连通块就会合并成一个,而对应的新边的两个点往后就不会再被枚举到(因为每次都是从两个不同的连通块中选点)。

  再考虑完所有不同P(u,v)的情况,那么最终的答案就是i=1n1(Swi+1)szuiszvi1

  AC代码如下,时间复杂度为O(nlogn)

复制代码
 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 
 4 typedef long long LL;
 5 
 6 const int N = 2e5 + 10, mod = 998244353;
 7 
 8 struct Node {
 9     int v, w, wt;
10 }e[N];
11 int fa[N], sz[N];
12 
13 int find(int x) {
14     return fa[x] == x ? fa[x] : fa[x] = find(fa[x]);
15 }
16 
17 int qmi(int a, LL k) {
18     int ret = 1;
19     while (k) {
20         if (k & 1) ret = 1ll * ret * a % mod;
21         a = 1ll * a * a % mod;
22         k >>= 1;
23     }
24     return ret;
25 }
26 
27 void solve() {
28     int n, m;
29     scanf("%d %d", &n, &m);
30     for (int i = 0; i < n - 1; i++) {
31         scanf("%d %d %d", &e[i].v, &e[i].w, &e[i].wt);
32     }
33     sort(e, e + n - 1, [&](Node &a, Node &b) {
34         return a.wt < b.wt;
35     });
36     for (int i = 1; i <= n; i++) {
37         fa[i] = i;
38         sz[i] = 1;
39     }
40     int ret = 1;
41     for (int i = 0; i < n - 1; i++) {
42         int x = find(e[i].v), y = find(e[i].w);
43         ret = 1ll * ret * qmi(m - e[i].wt + 1, (1ll * sz[x] * sz[y] - 1)) % mod;
44         sz[y] += sz[x];
45         fa[x] = y;
46     }
47     printf("%d\n", ret);
48 }
49 
50 int main() {
51     int t;
52     scanf("%d", &t);
53     while (t--) {
54         solve();
55     }
56     
57     return 0;
58 }
复制代码

 

参考资料

  Codeforces Round 891 (Div. 3) Editorial:https://codeforces.com/blog/entry/119134

posted @   onlyblues  阅读(131)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
历史上的今天:
2022-08-08 能量石
Web Analytics
点击右上角即可分享
微信分享提示