題意
給一個個點條邊的簡單無向圖,定義一棵生成樹的權值爲其邊權和與邊權的乘積。求所有生成樹的權值和。
分析
將拆成歐拉函數求和的形式,得到
問題轉化爲如何求所有生成樹的權值和。
最暴力的方法是枚舉一條邊,強制這條邊在生成樹中,然後把基爾霍夫矩陣中這條邊所在的行和列刪掉,對剩下的階子式求行列式,再乘上該邊的邊權,求和就是答案。那麼我們可以把一條邊的邊權看做多項式,然後求得行列式的一次項係數就是答案。因爲若在行列式中選取這一項,等價於強制這條邊在生成樹中,並將生成樹數量乘上邊權加入到答案中。因此只需要在模意義下求行列式即可。
對於時間複雜度,因爲每條邊最多被加入次,且若某次選出來的邊數小於,顯然貢獻必然爲。因此複雜度的一個上界爲。
代碼
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 35;
const int M = 160005;
const int MOD = 998244353;
int n, m, tot, prime[M], phi[M], p1[N * N], p2[N * N];
bool not_prime[M];
vector<int> edg[M];
int ksm(int x, int y)
{
int ans = 1;
while (y)
{
if (y & 1) ans = (LL)ans * x % MOD;
x = (LL)x * x % MOD; y >>= 1;
}
return ans;
}
struct poly
{
int x, y;
poly operator + (const poly & a) {return (poly){(x + a.x) % MOD, (y + a.y) % MOD};}
poly operator - (const poly & a) {return (poly){(x - a.x) % MOD, (y - a.y) % MOD};}
poly operator * (const poly & a) {return (poly){(LL)x * a.x % MOD, ((LL)x * a.y + (LL)y * a.x) % MOD};}
poly operator * (const int & a) {return (poly){(LL)x * a % MOD, (LL)y * a % MOD};}
poly inv() {int w = ksm(x, MOD - 2); return (poly){w, (LL)-w * w % MOD * y % MOD};}
}a[N][N];
void get_prime(int n)
{
phi[1] = 1;
for (int i = 2; i <= n; i++)
{
if (!not_prime[i]) prime[++tot] = i, phi[i] = i - 1;
for (int j = 1; j <= tot && i * prime[j] <= n; j++)
{
not_prime[i * prime[j]] = 1;
if (i % prime[j] == 0)
{
phi[i * prime[j]] = phi[i] * prime[j];
break;
}
phi[i * prime[j]] = phi[i] * (prime[j] - 1);
}
}
}
poly solve(int n)
{
poly ans = (poly){1, 0};
for (int i = 1; i <= n; i++)
{
int pos = i;
for (int j = i; j <= n; j++)
if (a[j][i].x || a[j][i].y) {pos = j; break;}
if (pos != i)
{
ans = ans * (-1);
for (int j = i; j <= n; j++)
swap(a[i][j], a[pos][j]);
}
ans = ans * a[i][i];
for (int j = i + 1; j <= n; j++) if (a[j][i].x || a[j][i].y)
{
poly w = a[j][i] * a[i][i].inv();
for (int k = i; k <= n; k++)
a[j][k] = a[j][k] - a[i][k] * w;
}
}
return ans;
}
int main()
{
scanf("%d%d", &n, &m);
int mx = 0;
for (int i = 1; i <= m; i++)
{
int w; scanf("%d%d%d", &p1[i], &p2[i], &w);
edg[w].push_back(i);
mx = max(mx, w);
}
get_prime(mx);
int ans = 0;
for (int i = 1; i <= mx; i++)
{
int sum = 0;
for (int j = i; j <= mx; j += i) sum += edg[j].size();
if (sum < n - 1) continue;
for (int j = 1; j <= n; j++)
for (int k = 1; k <= n; k++)
a[j][k] = (poly){0, 0};
for (int j = i; j <= mx; j += i)
for (int id : edg[j])
{
int x = p1[id], y = p2[id];
poly t = (poly){1, j};
a[x][x] = a[x][x] + t; a[y][y] = a[y][y] + t;
a[x][y] = a[x][y] - t; a[y][x] = a[y][x] - t;
}
poly res = solve(n - 1);
(ans += (LL)res.y * phi[i] % MOD) %= MOD;
}
printf("%d\n", (ans + MOD) % MOD);
return 0;
}