【题解 | 两种做法】洛谷 P4208 [JSOI2008] 最小生成树计数 [矩阵树/枚举]

特别难调,洛谷题解区很多人代码可读性不强,做的我怀疑人生。

(虽然我的码风也一般就是了)


前置知识:

Kruskal 求最小生成树。

题面:

洛谷 P4208

两种做法,一种矩阵树一种枚举。

(1)矩阵树定理

还没学过的指路这篇。

都知道矩阵树定理能算生成树个数,但本题要求最小生成树个数,不能直接使用。

观察发现:

同一无向连通图中,不同最小生成树各个权值的边的数量相同的。

简单证明下

如果存在两个最小生成树,一个选了 a-b 和 b-c 这两条边,

一个选了 a-d 和 d-c,其他边都相同。

其中 a-b 的权值小于 a-d,而且两对边的权值和相同。

那我们就肯定可以选 a-b 和 d-c,这样能得出更小的生成树,矛盾。

(肯定有人会问:你怎么能假定俩生成树其他边一样呢,难到不能通过其他边到这四个点吗?

    笨,要是能到值还更小,那一开始不就选了吗)

我们考虑先用 Kruskal 算法求出最小生成树的边集

对于权值为 i 的边,把边集里其他权值不为 i 的边加到图里,用并查集缩点

(因为每个权值的边能减少的连通块数量是固定的,只加最小生成树里的就好。

    绝对不能把边集里所有权值不为 i 的边一股脑全加进去!!那样出来的就不是最小生成树了!)

而边集里所有权值为 i 的边加到基尔霍夫矩阵里,在缩点的图上求生成树数量

(这个时候求生成树就保证选的 i 权值边的数量和一开始求最小生成树 i 权值边的数量一致!)

最后再把每个行列式乘到一起,就是答案。

时间复杂度:O(2^{10}M)

(M 是总边数)

代码思路不难,难的是调试,注意细节,别打错了。

#include<bits/stdc++.h>
using namespace std;typedef long long LL;
const int N = 2e3 + 10;
const LL P = 31011;
int fa[N];int findfa(int x) {   //并查集路径压缩 if(fa[x] == x) {return x;}return fa[x] = findfa(fa[x]);
}struct node {int x, y;LL c;
} a[N];bool cmp(node na, node nb) {return na.c < nb.c;
}LL L[N][N];  //基尔霍夫/拉普拉斯矩阵 
void add(int x, int y) {L[x][y] --; L[y][x] --;L[x][x] ++; L[y][y] ++;
}int n, m;LL gauss(int nn) {   //高斯消元求行列式 nn--;int r = 1;LL res = 1;for (int c = 1; c <= nn; c++) {for (int i = r + 1; i <= nn; i++) {while (L[i][c]) {LL bs = L[r][c] / L[i][c];for (int j = 1; j <= nn; j++) {L[r][j] -= L[i][j] * bs;}swap(L[r], L[i]);res *= -1;}}if (L[r][c] != 0) {r ++;}}if (r <= nn) {   // 非连通图,生成树数量为0return 0;}for (int i = 1; i <= nn; i++) {res = res * L[i][i] %P;}return res;
}map<LL, LL> mp;   //用来判断这条边的权值在不在最小生成树边集里 
int b[N], e[N];   //b:缩点后点的编号,e:最小生成树边集 int main() {ios::sync_with_stdio(false);cin.tie(0);cin >> n >> m;for (int i = 1; i <= m; i++) {cin >> a[i].x >> a[i].y >> a[i].c;}for (int i = 1; i <= n; i++) {   //并查集初始化 fa[i] = i;}int len = 0;sort (a + 1, a + m + 1, cmp);for (int i = 1; i <= m; i++) {   //先跑一遍 Kruskal int tx = findfa(a[i].x);int ty = findfa(a[i].y);if (tx != ty) {mp[a[i].c] = 1;fa[tx] = ty;e[++len] = i;}}LL ans = 1;for (int i = 1; i <= len; i++) if(mp[a[e[i]].c]) {for (int j = 1; j <= n; j++) {fa[j] = j;     //再初始化一编,因为除了权值为 a[e[i]].c 边还要跑一遍缩点 }for (int j = 1; j <= len; j++) {if(a[e[j]].c != a[e[i]].c) {int tx = findfa(a[e[j]].x);int ty = findfa(a[e[j]].y);if (tx != ty) {fa[tx] = ty;}}}int tmp = 0;  //缩点后有几个点 for (int j = 1; j <= n; j++) if (findfa(j) == j) {tmp ++;b[j] = tmp;}memset(L, 0, sizeof(L));for (int j = 1; j <= m; j++) {if(a[j].c == a[e[i]].c) {int tx = findfa(a[j].x);int ty = findfa(a[j].y);if(b[tx] != b[ty]) {   //不在一个连通块里 add(b[tx], b[ty]);   //加到基尔霍夫矩阵里 }}else if(a[j-1].c == a[e[i]].c){break;    //边集已经排过序,可以直接退出 }}ans = ans * gauss(tmp) %P;   //乘法原理行列式 mp[a[e[i]].c] = 0;   //遍历过就等于 0}cout << ans << "\n";return 0;
}

(2)dfs 枚举

首先还是 Kruskal 算法确定最小生成树,并统计每种权值的数量

对于每种权值,深搜枚举该权值的边是否选择,最终返回可行方案数

将所有权值的方案数相乘,得到总的最小生成树数量

时间复杂度:O(Mlog\,M+N^3)

(M 是总边数,N 是点数)

直接看代码吧,我写了注释:

#include<bits/stdc++.h>
using namespace std;typedef long long LL;
const int N = 2e3 + 10;
const LL P = 31011;struct node{int x, y;LL c;
} a[N];bool cmp(node na, node nb) {return na.c < nb.c;
}map<LL, LL> mp;  //存边权对应离散值的 
int n, m;int fa[N];
int findfa(int x) {   //并查集 if (fa[x] == x) {return fa[x];}return fa[x] = findfa(fa[x]);
}LL num[N], res;void dfs(int now, int cnt, LL nowc) {  //now:当前节点,cnt:当前权值选了几条边,nowc:当前权值 if (cnt == num[mp[nowc]]) {   //选够了就退出 res = (res + 1) %P;return ;}if (a[now].c != nowc) {  //越界了,选到别的权值区域 return ;}int pre[N];  //存档 fa数组,一定一定要在函数内定义!!不然迭代之前的数据就不见了 for (int i = 1; i <= n; i++) {   pre[i] = fa[i];}int tx = findfa(a[now].x);int ty = findfa(a[now].y);if (tx != ty) {fa[tx] = ty;dfs(now + 1, cnt + 1, nowc);   //把当前边加进去 }for (int i = 1; i <= n; i++) {fa[i] = pre[i];}dfs(now + 1, cnt, nowc);   //不加当前边 
}int main() {ios::sync_with_stdio(false);cin.tie(0);cin >> n >> m;int len = 0;for (int i=1 ; i <= m; i++) {cin >> a[i].x >> a[i].y >> a[i].c;if (!mp[a[i].c]) {len ++;mp[a[i].c] = len;   //边权离散值 }}sort(a + 1, a + m + 1, cmp);for (int i = 1; i <= n; i++) {fa[i] = i;     //并查集初始化 }int sum = 0;memset (num, 0, sizeof(num));for (int i = 1; i <= m; i++) {  //Kruskalint tx = findfa(a[i].x);int ty = findfa(a[i].y);if (tx != ty) {sum ++;num[mp[a[i].c]] ++;fa[tx] = ty;}}if (sum < n - 1) {cout << "0" << "\n";return 0;}for (int i = 1; i <= n; i++) {fa[i] = i;    //再来 }LL ans = 1;for (int i = 1; i <= m; i++) if(mp[a[i].c]) {  //还没被 dfs过的最小生成树权值 res = 0;dfs(i, 0, a[i].c);   ans = ans * res %P;for (int j = i; j <= m; j++) {if (a[j].c == a[i].c) {   //把当前权值的边都加进去 int tx = findfa(a[j].x);int ty = findfa(a[j].y);if (tx != ty) {fa[tx] = ty;}	}else if (a[j - 1].c == a[i].c) {   //越界 break;}}mp[a[i].c] = 0;    //dfs过了当前权值,之后就不用了 }cout << ans << "\n";return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/95051.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/95051.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

光谱相机多层镀膜技术如何提高透过率

光谱相机多层镀膜技术通过精密的光学设计与材料组合实现透过率提升&#xff0c;其核心原理与技术特性如下&#xff1a;一、多层镀膜的光学优化机制‌复合相位调控‌ 通过交替沉积高折射率&#xff08;如TiO₂, n2.3&#xff09;与低折射率材料&#xff08;如SiO₂, n1.46&#…

ubantu安装配置hive

在Ubuntu系统上安装Hive通常涉及几个步骤&#xff0c;包括安装Java&#xff08;因为Hive依赖于Java&#xff09;&#xff0c;安装Hadoop&#xff0c;然后安装Hive本身。以下是一个基本的步骤指南&#xff1a; 安装Java 首先&#xff0c;确保你的系统上安装了Java。你可以通过运…

大模型RAG项目实战:文本向量模型>Embedding模型、Reranker模型以及ColBERT模型

注&#xff1a;此文章内容均节选自充电了么创始人&#xff0c;CEO兼CTO陈敬雷老师的新书《GPT多模态大模型与AI Agent智能体》&#xff08;跟我一起学人工智能&#xff09;【陈敬雷编著】【清华大学出版社】 清华《GPT多模态大模型与AI Agent智能体》书籍配套视频课程【陈敬雷…

基于uni-app的校园综合服务平台开发实战

闪递校园&#xff1a;基于uni-app的校园综合服务平台开发实战作为一名全栈开发者&#xff0c;我用6个月时间开发了这款校园综合服务平台——闪递校园。本文将详细分享项目从0到1的开发经验&#xff0c;包括技术选型、核心功能实现、踩坑记录以及性能优化等方面的干货内容。&…

Qt::Q_INIT_RESOURCE用法

q_init_resource 用法 q_init_resource 是 Qt 框架中用于初始化嵌入式资源的一个函数。它通常用于将编译到应用程序二进制文件中的资源&#xff08;如图像、QML文件、翻译文件等&#xff09;注册到Qt的资源系统中。 基本用法 cpp Q_INIT_RESOURCE(resourcename); 其中 resource…

【开题答辩全过程】以 基于php的校园兼职求职网站为例,包含答辩的问题和答案

个人简介一名14年经验的资深毕设内行人&#xff0c;语言擅长Java、php、微信小程序、Python、Golang、安卓Android等开发项目包括大数据、深度学习、网站、小程序、安卓、算法。平常会做一些项目定制化开发、代码讲解、答辩教学、文档编写、也懂一些降重方面的技巧。感谢大家的…

安卓悬浮球-3566-测试报告

安卓悬浮球-3566-测试报告 测试概述 项目名称: 悬浮球电子秤应用 测试版本: v1.0.0 测试时间: 2025年9月 测试环境: UniApp开发环境 测试类型: 功能测试、性能测试、兼容性测试 测试结果: 见附件测试环境配置 硬件环境 测试设备: Android 内置3566屏幕分辨率: 1080x1920内存: 2…

《C++进阶之STL》【红黑树】

【红黑树】目录前言&#xff1a;------------概念介绍------------1. 什么是红黑树&#xff1f;2. 红黑树的基本特性是什么&#xff1f;3. 红黑树的效率怎么样&#xff1f;4. 红黑树如何确保最长路径不超过最短路径的2倍&#xff1f;------------基本操作------------一、查找操…

Java全栈工程师的实战面试:从基础到微服务

Java全栈工程师的实战面试&#xff1a;从基础到微服务 在一次真实的面试中&#xff0c;一位经验丰富的Java全栈开发工程师被问及多个技术问题。他的名字是林浩然&#xff0c;28岁&#xff0c;拥有计算机科学与技术硕士学位&#xff0c;拥有5年的工作经验。他曾在一家大型互联网…

工业物联网(IIoT)+ AI:智能工业的未来趋势全解析

工业物联网&#xff08;IIoT&#xff09; AI&#xff1a;智能工业的未来趋势全解析 文章目录工业物联网&#xff08;IIoT&#xff09; AI&#xff1a;智能工业的未来趋势全解析摘要什么是工业物联网&#xff08;IIoT&#xff09;&#xff1f;1. IIoT 的定义2. IIoT 与传统 IoT …

3000. 对角线最长的矩形的面积

3000. 对角线最长的矩形的面积 题目链接&#xff1a;3000. 对角线最长的矩形的面积 代码如下&#xff1a; class Solution { public:int areaOfMaxDiagonal(vector<vector<int>>& dimensions) {double maxDiagonalLength 0;int res 0;for (vector<int&g…

Scikit-learn Python机器学习 - 什么是机器学习

锋哥原创的Scikit-learn Python机器学习视频教程&#xff1a; 2026版 Scikit-learn Python机器学习 视频教程(无废话版) 玩命更新中~_哔哩哔哩_bilibili 课程介绍 本课程主要讲解基于Scikit-learn的Python机器学习知识&#xff0c;包括机器学习概述&#xff0c;特征工程(数据…

Python环境搭建报错

检查Python版本兼容性确保下载的Python版本与操作系统匹配&#xff08;如Windows 32位/64位、macOS ARM/x86&#xff09;。可通过命令行输入python --version或python3 --version验证已安装版本是否与需求一致。清理残留文件若之前安装失败&#xff0c;需手动删除残留文件。Win…

C# WinForm中提供webapi服务

云川给我提了一个需求&#xff0c;要我开发一个API服务程序&#xff0c;他来调用&#xff0c;程序再去明道云取数&#xff0c;计算出一个结果返回。网上找到了一篇文章&#xff1a;C# 在Windform程序中搭建Webapi - 小码哥-风云 - 博客园&#xff0c;可以使用微软提供的Microso…

Linux中使用docker部署solr

1. 运行一次&#xff0c;然后拉取镜像 [rootinstance-yo4hab98 ~]# docker run -d -p 8983:8983 --name solr-8.11.3 -t solr:8.11.3 ps 镜像相关指令 # 查看镜像 docker images# 删除镜像 指定名称和版本删除 docker rmi nginx:latest # 删除镜像 指定id删除 docker rm…

代谢组学分析指南

摘要代谢组学是个新兴领域&#xff0c;系统性地定量众多代谢物。关键目的是识别与每种生物表型相对应的代谢物&#xff0c;并进一步分析其中涉及的机制。尽管代谢组学对于理解相关的生物学现象至关重要&#xff0c;但在全面描述过程的能力上存在局限性。推荐采用综合分析策略&a…

vue2使用el-form动态参数展示并非空校验

需求&#xff1a;需要根据类型type动态显示某些参数&#xff0c;并且后端需要的参数也不同&#xff0c;比如type为1&#xff1a;后端要aa和bb参数&#xff0c;type为2&#xff1a;后端要cc和dd参数&#xff0c;前端显示的字段名也不一样&#xff0c;但是样式是不变的。1.效果2.…

(附源码)基于Vue的教师档案管理系统的设计与实现

摘 要 随着信息技术的不断发展&#xff0c;学校管理工作正逐渐从纸质化向数字化转型。教师档案管理作为学校管理的重要环节&#xff0c;其信息化和高效化对于提升学校管理水平具有重要意义。本文设计并实现了一个基于Vue框架的教师档案管理系统&#xff0c;旨在通过前端技术的…

运算电源抑制比(PSRR)测量及设计注意事项

1、简介如果运放的供电电源发生变化&#xff0c;输出不应发生变化&#xff0c;但实际运放随着供电电源的波动&#xff0c;运放输出也将会发生波动。折合到输出端&#xff0c;PSRR定义 Xv(电源电压波动) / Yv&#xff08;输出电压波动&#xff09;&#xff0c;该量为无量纲&…

YOLOv8-SMOT:一种高效鲁棒的实时小目标跟踪框架:基于切片辅助训练与自适应关联

https://arxiv.org/pdf/2507.12087 摘要 从无人机&#xff08;UAV&#xff09;视角对小型敏捷多目标&#xff08;SMOT&#xff09;——例如鸟类——进行跟踪是一项极具挑战性的计算机视觉任务。该任务的难点主要源于三个方面&#xff1a;目标外观特征极度稀缺、相机与目标自身复…