【洛谷 P6624 [省选联考 2020 A 卷] 作业题】【矩阵树定理】

题意

给一个 n n n个点 m m m条边的简单无向图,定义一棵生成树的权值为其边权和与边权 gcd ⁡ \gcd gcd的乘积。求所有生成树的权值和。
n ≤ 30 , w i ≤ 152501 n\le 30,w_i\le 152501 n30,wi152501

分析

gcd ⁡ \gcd gcd拆成欧拉函数求和的形式,得到 a n s = ∑ w φ ( w ) ∗ [ 所 有 边 权 都 是 w 倍 数 的 生 成 树 权 值 和 ] ans=\sum_w\varphi(w)*[所有边权都是w倍数的生成树权值和] ans=wφ(w)[w]

问题转化为如何求所有生成树的权值和。

最暴力的方法是枚举一条边,强制这条边在生成树中,然后把基尔霍夫矩阵中这条边所在的行和列删掉,对剩下的 n − 1 n-1 n1阶子式求行列式,再乘上该边的边权,求和就是答案。那么我们可以把一条边的边权看做多项式 1 + w i x 1+w_ix 1+wix,然后求得行列式的一次项系数就是答案。因为若在行列式中选取 w i x w_ix wix这一项,等价于强制这条边在生成树中,并将生成树数量乘上边权加入到答案中。因此只需要在模 x 2 x^2 x2意义下求行列式即可。

对于时间复杂度,因为每条边最多被加入 σ 0 ( w i ) \sigma_0(w_i) σ0(wi)次,且若某次选出来的边数小于 n − 1 n-1 n1,显然贡献必然为 0 0 0。因此复杂度的一个上界为 O ( n 5 σ 0 ( w i ) n − 1 ) = O ( n 4 σ 0 ( w i ) ) O(n^5\frac{\sigma_0(w_i)}{n-1})=O(n^4\sigma_0(w_i)) O(n5n1σ0(wi))=O(n4σ0(wi))

代码

#include
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;
}

你可能感兴趣的:(矩阵树定理)