【洛谷 P6624 [省選聯考 2020 A 卷] 作業題】【矩陣樹定理】

題意

給一個nn個點mm條邊的簡單無向圖,定義一棵生成樹的權值爲其邊權和與邊權gcd\gcd的乘積。求所有生成樹的權值和。
n30,wi152501n\le 30,w_i\le 152501

分析

gcd\gcd拆成歐拉函數求和的形式,得到ans=wφ(w)[w]ans=\sum_w\varphi(w)*[所有邊權都是w倍數的生成樹權值和]

問題轉化爲如何求所有生成樹的權值和。

最暴力的方法是枚舉一條邊,強制這條邊在生成樹中,然後把基爾霍夫矩陣中這條邊所在的行和列刪掉,對剩下的n1n-1階子式求行列式,再乘上該邊的邊權,求和就是答案。那麼我們可以把一條邊的邊權看做多項式1+wix1+w_ix,然後求得行列式的一次項係數就是答案。因爲若在行列式中選取wixw_ix這一項,等價於強制這條邊在生成樹中,並將生成樹數量乘上邊權加入到答案中。因此只需要在模x2x^2意義下求行列式即可。

對於時間複雜度,因爲每條邊最多被加入σ0(wi)\sigma_0(w_i)次,且若某次選出來的邊數小於n1n-1,顯然貢獻必然爲00。因此複雜度的一個上界爲O(n5σ0(wi)n1)=O(n4σ0(wi))O(n^5\frac{\sigma_0(w_i)}{n-1})=O(n^4\sigma_0(w_i))

代碼

#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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章