[省选联考 2020 A 卷] 作业题(欧拉反演 + 矩阵树定理 + 高斯消元)

problem

luogu-P6624

小 W 刚刚在离散数学课学习了生成树的知识:一个无向图 G = ( V , E ) G=(V,E) G=(V,E) 的生成树 T T T 为边集 E E E 的一个大小为 ∣ V ∣ − 1 |V|-1 V1 的子集,且保证 T T T 的生成子图在 G G G 中连通。

小 W 在做今天的作业时被这样一道题目难住了:

给定一个 n n n 个顶点 m m m 条边(点和边都从 1 1 1 开始编号)的无向图 G G G,保证图中无重边和无自环。每一条边有一个正整数边权 w i w_i wi,对于一棵 G G G 的生成树 T T T,定义 T T T 的价值为: T T T 所包含的边的边权的最大公约数乘以边权之和。

即: v a l ( T ) = ( ∑ i = 1 n − 1 w e i ) × gcd ⁡ ( w e 1 , w e 2 , … , w e n − 1 ) val(T)=\left(\sum\limits_{i=1}^{n-1} w_{e_i}\right) \times \gcd(w_{e_1},w_{e_2},\dots,w_{e_{n-1}}) val(T)=(i=1n1wei)×gcd(we1,we2,,wen1)

其中 e 1 , e 2 , … , e n − 1 e_1,e_2,\dots,e_{n-1} e1,e2,,en1 T T T 包含的边的编号。

小 W 需要求出 G G G 的所有生成树 T T T 的价值之和,他做了很久也没做出来,请你帮帮他。

由于答案可能很大,你只需要给出答案对 998244353 取模后的结果。

solution

众所周知: ∑ d ∣ n φ ( d ) = n \sum_{d\mid n}\varphi(d)=n dnφ(d)=n
∑ T ∑ i = 1 n − 1 w e i × gcd ⁡ ( w e 1 , w e 2 , . . . , w e n − 1 ) \sum_T\sum_{i=1}^{n-1}w_{e_i}\times \gcd(w_{e_1},w_{e_2},...,w_{e_{n-1}}) Ti=1n1wei×gcd(we1,we2,...,wen1)

= ∑ i = 1 n − 1 w e i × ∑ d ∣ w e 1 ∧ ⋯ ∧ d ∣ w e n − 1 φ ( d ) =\sum_{i=1}^{n-1}w_{e_i}\times \sum_{d\mid w_{e_1}\wedge\dots\wedge d\mid w_{e_{n-1}}}\varphi(d) =i=1n1wei×dwe1dwen1φ(d)

= ∑ d = 1 max ⁡ { w i } d ∑ T ∧ d ∣ w e 1 ⋯ ∧ d ∣ w e n − 1 ∑ i = 1 n − 1 w e i =\sum_{d=1}^{\max\{w_i\}}d\sum_{T\wedge d\mid w_{e_1}\dots\wedge d\mid w_{e_{n-1}}}\sum_{i=1}^{n-1}w_{e_i} =d=1max{wi}dTdwe1dwen1i=1n1wei

外层枚举 d d d,内层则是求所有生成树的边权和。

矩阵树定理:

  • 若为无向图,求生成树个数。

    a ( i , j ) = − m ( i , j ) a(i,j)=-m(i,j) a(i,j)=m(i,j),其中 m ( i , j ) : i , j m(i,j):i,j m(i,j):i,j 之间有多少边, a ( i , i ) = i a(i,i)=i a(i,i)=i 的度数。

    随意去除矩阵 a a a 的某一行某一列(习惯上是去掉最后一行最后一列)后,生成树个数即为新矩阵的行列式。

  • 若为有向图,给定根,求外向生成树(出度为 1 1 1)个数。

    a ( i , j ) = − m ( i , j ) a(i,j)=-m(i,j) a(i,j)=m(i,j),其中 m ( i , j ) : i , j m(i,j):i,j m(i,j):i,j 之间有多少边, a ( i , i ) = i a(i,i)=i a(i,i)=i 的入度度数。

    去掉根所在的行和列后的矩阵行列式即为答案。

  • 若为有向图,给定根,求内向生成树(入度为 1 1 1)个数。

    a ( i , j ) = − m ( i , j ) a(i,j)=-m(i,j) a(i,j)=m(i,j),其中 m ( i , j ) : i , j m(i,j):i,j m(i,j):i,j 之间有多少边, a ( i , i ) = i a(i,i)=i a(i,i)=i 的出度度数。

    去掉根所在的行和列后的矩阵行列式即为答案。

事实上,矩阵树定理求出的行列式可以是所有生成树边权积的和,求生成树个数只是钦定所有 w e i = 1 w_{e_i}=1 wei=1 罢了。

但这里是让求所有生成树边权和的和。

所以我们可以构造生成函数 ( 1 + w i x ) (1+w_ix) (1+wix) 表示第 i i i 条边的边权。

然后求行列式,这样一次项的系数即为答案。

对于 ( a + b x ) (a+bx) (a+bx) 四则运算:

  • 加减法照样相应位加减。
  • 乘法 ( a + b x ) ( c + d x ) = a c + ( d c + a d ) x (a+bx)(c+dx)=ac+(dc+ad)x (a+bx)(c+dx)=ac+(dc+ad)x ,二次项直接抛掉即可。
  • 除法 a + b x c + d x = a c + b c − a d c 2 x \frac{a+bx}{c+dx}=\frac{a}{c}+\frac{bc-ad}{c^2}x c+dxa+bx=ca+c2bcadx,同理直接抛掉二次项。

code

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define mod 998244353
#define Pair pair < int, int >
#define maxn 500
#define maxv 153000
int n, m, cnt;
int phi[maxv], vis[maxv], prime[maxv];
int u[maxn], v[maxn], w[maxn], g[maxv];
Pair a[maxn][maxn];

int qkpow( int x, int y ) {
    int ans = 1;
    while( y ) {
        if( y & 1 ) ans = ans * x % mod;
        x = x * x % mod;
        y >>= 1;
    }
    return ans;
}

Pair operator + ( Pair x, Pair y ) {
    return make_pair( (x.first + y.first) % mod, (x.second + y.second) % mod );
}
Pair operator - ( Pair x, Pair y ) {
    return make_pair( (x.first - y.first) % mod, (x.second - y.second) % mod );
}
Pair operator * ( Pair x, Pair y ) {
    return make_pair( x.first * y.first % mod, (x.first * y.second + x.second * y.first) % mod );
}
Pair operator / ( Pair x, Pair y ) {
    int inv = qkpow( y.first, mod - 2 );
    return make_pair( x.first * inv % mod, (x.second * y.first - x.first * y.second) % mod * inv % mod * inv % mod );
}

void divide( int x ) {
    for( int i = 1;i * i <= x;i ++ )
        if( x % i == 0 ) {
            ++ g[i];
            if( i * i != x ) ++ g[x / i];
        }
}

void sieve( int n ) {
    phi[1] = 1;
    for( int i = 2;i <= n;i ++ ) {
        if( ! vis[i] ) phi[i] = i - 1, prime[++ cnt] = i;
        for( int j = 1;j <= cnt and i * prime[j] <= n;j ++ ) {
            vis[i * prime[j]] = 1;
            if( i % prime[j] == 0 ) {
                phi[i * prime[j]] = phi[i] * prime[j];
                break;
            }
            else
                phi[i * prime[j]] = phi[i] * phi[prime[j]];
        }
    }
}

int Guass( int n ) {
    Pair ans = make_pair( 1, 0 );
    int tag = 0;
    for( int i = 1;i <= n;i ++ ) {
        if( ! a[i][i].first ) {
            for( int j = i + 1;j <= n;j ++ )
                if( a[j][i].first ) {
                    tag ^= 1;
                    swap( a[i], a[j] );
                    break;
                }
        }
        for( int j = i + 1;j <= n;j ++ ) {
            Pair o = a[j][i] / a[i][i];
            for( int k = i;k <= n;k ++ )
                a[j][k] = a[j][k] - o * a[i][k];
        }
        ans = ans * a[i][i];
    }
    ans = tag ? make_pair( 0, 0 ) - ans : ans;
    return ans.second;
}

int solve( int x ) {
    memset( a, 0, sizeof( a ) );
    for( int i = 1;i <= m;i ++ )
        if( w[i] % x == 0 ) {
            a[u[i]][u[i]] = a[u[i]][u[i]] + make_pair( 1, w[i] );
            a[v[i]][v[i]] = a[v[i]][v[i]] + make_pair( 1, w[i] );
            a[u[i]][v[i]] = a[u[i]][v[i]] - make_pair( 1, w[i] );
            a[v[i]][u[i]] = a[v[i]][u[i]] - make_pair( 1, w[i] );
        }
    return Guass( n - 1 );
}

signed main() {
    scanf( "%lld %lld", &n, &m );
    int Max = 0;
    for( int i = 1;i <= m;i ++ ) {
        scanf( "%lld %lld %lld", &u[i], &v[i], &w[i] );
        divide( w[i] );
        Max = max( Max, w[i] );
    }
    sieve( Max );
    int ans = 0;
    for( int i = 1;i <= Max;i ++ )
        if( g[i] >= n - 1 ) 
            (ans += solve( i ) * phi[i]) %= mod;
    printf( "%lld\n", (ans + mod) % mod );
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值