2020 Multi-University Training Contest 6

问题描述




题解

题目是求随机选出一个生成树的权值期望,突破点在于生成树的权值定义,是树上所有边权按位与之后的结果。期望最基础的性质就是期望的和等于和的期望,即(E(X + Y) = E(X) + E(Y))。知道这个性质那么有一个很正常的想法就是拆位,因为每一位在按位与的过程中都是相互独立的。

现在我们直接枚举二进制的每一位(i),对于这一位,如果(w_{u, v})的第(i)位不为(0),那么我们就直接在(u, v)直接连一条边。那么对于这张新图,每有一个生成树,那么都会对答案贡献(frac{2^i}{sum})(sum)是指原图中生成树的个数。用无向图的矩阵树定理就能求出生成树的个数,这道题的整体复杂度是(O(Tn^3log{w} ))


(mathrm{Code})

#include<bits/stdc++.h>
using namespace std;

const int maxm = 10000 + 7;
const int maxn = 100 + 3;
const int mod = 998244353;

int T, n, m;
int u[maxm], v[maxm], w[maxm];
int a[maxn][maxn];

#define int long long
int ksm(int b,int p){int ret=1;while(p){if(p&1)ret=1ll*ret*b%mod;b=1ll*b*b%mod;p>>=1;}return ret;}
#undef int

void Init(void) {
	scanf("%d%d", &n, &m);
}

void add(int x, int y) {
//	if(x > y) return;
	a[x][x]++, a[y][y]++;
	a[x][y]--, a[y][x]--;
}

int Gauss() {
	int ans=1;
	for(int i=1;i<n;++i) {
		for(int k=i+1;k<n;++k) {
			while(a[k][i]) {
				int d=a[i][i]/a[k][i];
				for(int j=i;j<n;++j) a[i][j]=(a[i][j]-1LL*d*a[k][j]%mod+mod)%mod;
				std::swap(a[i],a[k]),ans=-ans;
			}
		}
		ans=1LL*ans*a[i][i]%mod,ans=(ans+mod)%mod;
	}
	return ans;
}

long long ans;

void Work(void) {
	for(int i = 1; i <= m; i++) {
		scanf("%d%d%d", &u[i], &v[i], &w[i]);
	}
	for(int p = 0; p < 32; p++) {
		memset(a, 0, sizeof(a));
		for(int i = 1; i <= m; i++) {
			if(w[i] & (1ll << p)) add(u[i], v[i]);
		}
		int res = Gauss();
		ans =(ans + (long long)(1ll << p) * (long long)res) % mod;
	}
	memset(a, 0, sizeof(a));
	for(int i = 1; i <= m; i++) {
		add(u[i], v[i]);
	}
	int sum = Gauss();
	ans = ((ans * ksm((long long)sum, mod - 2)) % mod + mod) % mod;
	printf("%lld
", ans);
}

int main(void) {
	scanf("%d", &T);
	while(T--) {
		ans = 0;
		Init();
		Work();
	}
	return 0;
}
原文地址:https://www.cnblogs.com/liubainian/p/13447399.html