2020年HDU多校第六场 1010 Expectation(矩阵树)
2020年HDU多校第六场 1010 Expectation(矩阵树)
题意:给一个图,输出其随机生成树的权值的期望,其权值为树的所有边的与。
题解:要写这个题,需要知道一个知识点,矩阵树定理,它可以求出一个图内生成树的数量,放个板子。
#include<iostream>
using namespace std;
const int mod=1e9+7;
int n;
int f[207][207];
//f[i][i] 存i点的度数
//f[i][j] 存i点到j点的边数的相反数
//ans为图的生成树数量
ll gauss () {
ll ans = 1 ;
for (int i = 1; i <= n; i ++) {
for (int j = i + 1; j <= n; j ++) {
while (f[j][i]) {
ll t = f[i][i] / f[j][i] ;
for (int k = i; k < tot; k ++)
f[i][k] = (f[i][k] - t * f[j][k] + mod) % mod ;
swap (f[i], f[j]) ;
ans = -ans ;
}
}
ans = (ans * f[i][i]) % mod ;
}
return (ans + mod) % mod ;
}
然后,我们可以先求出生成树的总数,毕竟要求期望要除总数,然后算分子部分,由题意我们要求的是所有边的按位与,所以:
第一,我们将31位数分开算贡献,每位之间的答案互不影响;
第二,若要某位产生贡献,必然要其生成树的所有边都要有那一位,所以没有那一位的边不加入图。
意思就是枚举每一位,每次都重新建一次图用矩阵数定理算一次答案。
#include<iostream>
#include<cstring>
using namespace std;
#define ll long long
const ll mod=998244353;
ll n,m,t,sum;
ll f[207][207];
ll u[40007],v[40007],w[40007];
ll gauss () {
ll ans = 1 ;
for (int i = 1; i < n; i ++) {
for (int j = i + 1; j < n; j ++) {
while (f[j][i]) {
ll t = f[i][i] / f[j][i] ;
for (int k = i; k < n; k ++)
f[i][k] = (f[i][k] - t * f[j][k] + mod) % mod ;
swap (f[i], f[j]) ;
ans = -ans ;
}
}
ans = (ans * f[i][i]) % mod ;
}
return (ans + mod) % mod ;
}
long long int pow(long long int x,long long int n,long long int mod)
{
long long int res=1;
while(n>0)
{
if(n%2==1)
{
res=res*x;
res=res%mod;
}
x=x*x;
x=x%mod;
n>>=1;
}
return res;
}
void add(int u,int v){
f[u][u]++;f[v][v]++;
f[u][v]--;f[v][u]--;
}
void init(){
memset(f,0,sizeof(f));
}
int main(){
scanf("%lld",&t);
while(t--){
init();
scanf("%lld%lld",&n,&m);
for(int i=1;i<=m;i++){
cin>>u[i]>>v[i]>>w[i];
add(u[i],v[i]);
}
sum=gauss();
sum=pow(sum,mod-2,mod);
ll aqours=0,er=1;
for(int i=0;i<=30;i++){
init();
for(int i=1;i<=m;i++){
if(w[i]&er){
add(u[i],v[i]);
}
}
aqours=(aqours+gauss()*er%mod*sum%mod)%mod;
er*=2;
}
printf("%lld\n",aqours);
}
}