qwq

这个题目真的是很好的一个题啊

qwq

其实一开始想这个题,肯定是无从下手。

首先,我们会发现,对于无向图的一个最小生成树来说,只有当存在一些边与内部的某些边权值相同的时候且能等效替代的时候,才会有多种最小生成树。

那我们不妨对于原图先随意求一个最小生成树,然后对于出现在最小生成树上的每个权值计算贡献。

我们每次删除所有该权值的边,然后把剩下的点能缩点的进行缩点(用并查集来维护)

然后,我们构造一个联通块的拉普拉斯矩阵。也就是说,加入所有的在图中的,权值为该值的边。然后我们只需要求能重新构成生成树的连接方式。

(这里重边要当成不同的边来算!!因为表示的方案并不相同)

那么我们考虑对于当前权值的边的一个合法的连接,是要求能将所有的联通块变成一个树。

换句话说,对于每一条边,他的合法连接方式数量,就是这个图的生成树个数。

假设每个权值的合法连接方式是\(f[i]\)

那么最终的$$ans=\prod_{i \in tree} f[i]$$

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<set>
#define mk make_pair
#define ll long long
#define int long long
#include<unordered_map>
using namespace std;
inline int read()
{
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)) {if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)) {x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
return x*f;
}
const int maxn = 510;
const int mod = 31011;
const int maxm = 1e5+1e2;
struct Edge{
int u,v,w;
};
Edge e[maxm];
int tag[maxm];
int n,m;
int ans=1;
vector<int> v;
int fa[maxn];
int vis[maxm];
int a[maxn][maxn];
Edge now[maxm];
int sum;
bool cmp(Edge a,Edge b)
{
return a.w<b.w;
}
int find(int x)
{
if (fa[x]!=x) fa[x]=find(fa[x]);
return fa[x];
}
void kruskal()
{
sort(e+1,e+1+m,cmp);
for (int i=1;i<=n;i++) fa[i]=i;
for (int i=1;i<=m;i++)
{
int x=e[i].u;
int y=e[i].v;
int f1 = find(x);
int f2 = find(y);
if (f1==f2) continue;
tag[i]=1;
fa[f1]=fa[f2];
v.push_back(e[i].w);
++sum;
}
}
int gauss(int n)
{
int k=1;
int ans=1;
int ff=0;
for (int i=1;i<=n;i++)
{
int now =k;
while (now<=n && (!a[now][i])) now++;
if (now==n+1) continue;
if (now!=k) ff++;
for (int j=1;j<=n+1;j++) swap(a[now][i],a[k][i]);
for (int j=i+1;j<=n;j++)
{
while (a[j][i])
{
int t = a[k][i]/a[j][i];
for (int p=i;p<=n;p++) a[k][p]-=t*a[j][p];
swap(a[k],a[j]);
ff++;
}
}
ans=ans*a[i][i]%mod;
k++;
}
if(ff&1) ans=(mod-ans);
return ans;
}
int tt[maxn];
int ymh[maxn];
void count(int val)
{
memset(tt,0,sizeof(tt));
memset(a,0,sizeof(a));
memset(ymh,0,sizeof(ymh));
int num=0;
int top=0;
for (int i=1;i<=m;i++)
{
if (tag[i] && e[i].w!=val)
now[++top]=e[i],tt[e[i].u]++,tt[e[i].v]++;
else
if (e[i].w==val) tt[e[i].u]++,tt[e[i].v]++;
}
for (int i=1;i<=n;i++) fa[i]=i;
for (int i=1;i<=top;i++)
{
int x=now[i].u;
int y=now[i].v;
tt[x]++;
tt[y]++;
int f1 = find(x),f2=find(y);
if (x==y) continue;
fa[f1]=fa[f2];
}
for (int i=1;i<=n;i++)
{
if (!tt[i]) continue;
if (find(i)==i)
{
ymh[i]=++num;
}
}
for (int i=1;i<=m;i++)
{
if (e[i].w==val)
{
int x=ymh[find(e[i].u)];
int y=ymh[find(e[i].v)];
a[x][y]--;
a[y][x]--;
a[x][x]++;
a[y][y]++;
}
}
ans=ans*gauss(num-1)%mod;
}
signed main()
{
n=read(),m=read();
for (int i=1;i<=m;i++)
{
e[i].u=read();
e[i].v=read();
e[i].w=read();
}
kruskal();
if (sum!=n-1)
{
cout<<0;
return 0;
}
sort(v.begin(),v.end());
for (int i=0;i<v.size();i++)
{
if (i==0)count(v[i]);
else if (v[i]!=v[i-1]) count(v[i]);
}
cout<<ans;
return 0;
}

洛谷4208 JSOI2008最小生成树计数(矩阵树定理+高斯消元)的更多相关文章

  1. [spoj104][Highways] (生成树计数+矩阵树定理+高斯消元)

    In some countries building highways takes a lot of time... Maybe that's because there are many possi ...

  2. BZOJ4031 [HEOI2015]小Z的房间 【矩阵树定理 + 高斯消元】

    题目链接 BZOJ4031 题解 第一眼:这不裸的矩阵树定理么 第二眼:这个模\(10^9\)是什么鬼嘛QAQ 想尝试递归求行列式,发现这是\(O(n!)\)的.. 想上高斯消元,却又处理不了逆元这个 ...

  3. P3317 [SDOI2014]重建 变元矩阵树定理 高斯消元

    传送门:https://www.luogu.org/problemnew/show/P3317 这道题的推导公式还是比较好理解的,但是由于这个矩阵是小数的,要注意高斯消元方法的使用: #include ...

  4. luoguP4208 [JSOI2008]最小生成树计数 矩阵树定理

    题目大意: 求最小生成树的数量 曾今的我感觉这题十分的不可做 然而今天看了看,好像是个类模板的题.... 我们十分容易知道,记能出现在最小生成树中的边的集合为\(S\) 那么,只要是\(S\)中的边构 ...

  5. CF917D-Stranger Trees【矩阵树定理,高斯消元】

    正题 题目链接:https://www.luogu.com.cn/problem/CF917D 题目大意 给出\(n\)个点的一棵树,对于每个\(k\)求有多少个\(n\)个点的树满足与给出的树恰好有 ...

  6. Wannafly Camp 2020 Day 1D 生成树 - 矩阵树定理,高斯消元

    给出两幅 \(n(\leq 400)\) 个点的无向图 \(G_1 ,G_2\),对于 \(G_1\) 的每一颗生成树,它的权值定义为有多少条边在 \(G_2\) 中出现.求 \(G_1\) 所有生成 ...

  7. 洛谷P4457/loj#2513 [BJOI2018]治疗之雨(高斯消元+概率期望)

    题面 传送门(loj) 传送门(洛谷) 题解 模拟赛的时候只想出了高斯消元然后死活不知道怎么继续--结果正解居然就是高斯消元卡常? 首先有个比较难受的地方是它一个回合可能不止扣一滴血--我们得算出\( ...

  8. 【BZOJ3534】【Luogu P3317】 [SDOI2014]重建 变元矩阵树,高斯消元

    题解看这里,主要想说一下以前没见过的变元矩阵树还有前几个题见到的几个小细节. 邻接矩阵是可以带权值的.求所有生成树边权和的时候我们有一个基尔霍夫矩阵,是度数矩阵减去邻接矩阵.而所谓变元矩阵树实际上就是 ...

  9. SP104 Highways (矩阵树,高斯消元)

    矩阵树定理裸题 //#include <iostream> #include <cstdio> #include <cstring> #include <al ...

随机推荐

  1. freeswitch新增模块

    概述 freeswitch的架构由稳定的核心模块和大量的外围插件式模块组成.核心模块保持稳定,外围模块可以动态的加载/卸载,非常灵活方便. 外围模块通过核心提供的 Public API与核心进行通信, ...

  2. sychronized

    说一下 synchronized 底层实现原理? synchronized可以保证方法或者代码块在运行时, 同一时刻只有一个方法可以进入到临界区, 同时它还可以保证共享变量的内存可见性. Java中每 ...

  3. js获取文件名和后缀名

  4. 两种github action 打包.Net Core 项目docker镜像推送到阿里云镜像仓库

    两种github action 打包.Net Core 项目docker镜像推送到阿里云镜像仓库 1.GitHub Actions 是什么? 大家知道,持续集成由很多操作组成,比如抓取代码.运行测试. ...

  5. C#使用异步需要注意的几个问题

    C#异步使用需要注意的几个问题1.异步方法如果只是对别的方法的简单的转发调用,没哟复杂的逻辑(比如等待A的结果,再调用B,等待A调用的返回值拿到内部做一些处理再返回),那么就可以去掉async关键字. ...

  6. 前后端数据交互(二)——原生 ajax 请求详解

    一.ajax介绍 ajax 是前后端交互的重要手段或桥梁.它不是一个技术,是一组技术的组合. ajax :a:异步:j:js:a:和:x:服务端的数据. ajax的组成: 异步的 js 事件 其他 j ...

  7. vue 引用省市区三级联动(element-ui Cascader)

    npm 下载 npm install element-china-area-data -S main.js import {provinceAndCityData,regionData,provinc ...

  8. rsync 服务搭建

    rsync 服务搭建 服务端部署操作内容: 创建rsync用户和用户组 eg: useradd -s /sbin/nologin -M rsync 创建需要备份的指定目录,并修改权限 eg: mkdi ...

  9. Python - 头部解析

    背景 写 python 的时候,基本都要加两个头部注释,这到底有啥用呢? #!usr/bin/env python # -*- coding:utf-8 _*- print("hello-w ...

  10. Python - 面向对象编程 - __del__() 析构方法

    del 语句 Python 提供了 del 语句用于删除不再使用的变量 语法 del 表达式 删除变量的栗子 var = "hello" del var print(var) # ...