CF1857G Counting Graphs

题目链接

考虑每条非树边的取值,显然不能小于等于该边与树边形成的环中的最大值(当然这条非树边也可以不存在),所以每条非树边的取值范围就是 \(S - max(w) + 1\)\(+1\)的原因是该边可能不存在)。

暴力枚举肯定会超时,考虑优化。

发现 \(kruskal\) 算法获得最小生成树的过程中,按从小到大顺序取边,一条合法的边被取得时(假设这条边的边权为 \(w0\)),该边连接的两颗树,任意不在同一颗树上的两点之间的非树边的取值范围都是一样的,因为他们的 \(max(w)\) 都是 \(w0\) ,所以这些非树边的贡献之和为 \((S - w0 + 1) \times (sum1 * sum2 - 1)\)\(sum1,sum2\) 分别表示两棵树的节点数, \(-1\) 是因为 \(w0\) 不能算在内)。

\(kruskal\) 过程中的并查集变成带权并查集维护即可,时间复杂度 \(O(nlogn)\)

#include<bits/stdc++.h>

using namespace std;

typedef long long LL;
const int M = 998244353, N = 2e5;
struct Edge
{
    int x, y, z;
}a[N + 9];
int n, S, fa[N + 9], sum[N + 9];

bool cmp(Edge a, Edge b)
{
    return a.z < b.z;
}

int find(int x)
{
    if (fa[x] == x) return x;
    return fa[x] = find(fa[x]);
}

int ksm(int x, LL y)
{
    if (y == 0) return 1;
    if (y & 1) return 1ll * ksm(x, y - 1) * x % M;
    else
    {
        int k = ksm(x, y / 2);
        return 1ll * k * k % M;
    }
}

int main()
{
    int t;
    scanf("%d", &t);
    while (t--)
    {
        scanf("%d %d", &n, &S);
        for (int i = 1; i < n; i++)
            scanf("%d %d %d", &a[i].x, &a[i].y, &a[i].z);
        sort (a + 1, a + 1 + n - 1, cmp);
        for (int i = 1; i <= n; i++)
            fa[i] = i, sum[i] = 1;
        int ans = 1;
        for (int i = 1; i < n; i++)
        {
             if (find(a[i].x) == find(a[i].y))
                continue;
            int X = find(a[i].x), Y = find(a[i].y);
            if (a[i].z < S)
                ans = 1ll * ans * ksm(S - a[i].z + 1, 1ll * sum[X] * sum[Y] - 1) % M;
            fa[Y] = X, sum[X] += sum[Y];
        }
        printf("%d\n", ans);
    }
    return 0;
}

posted @ 2023-09-24 15:45  With_penguin  阅读(11)  评论(0编辑  收藏  举报