$bzoj1016-JSOI2008$ 最小生成树计数 最小生成树 $dfs/matrix-tree$定理
- 题面描述
- 现在给出了一个简单无向加权图。你不满足于求出这个图的最小生成树,而希望知道这个图中有多少个不同的最小生成树。(如果两颗最小生成树中至少有一条边不同,则这两个最小生成树就是不同的)。由于不同的最小生成树可能很多,所以你只需要输出方案数对\(31011\)的模就可以了。
- 输入格式
- 第一行包含两个数,\(n\)和\(m\),其中\(1\leq n\leq 10^2, 1\leq m\leq 10^3\) 表示该无向图的节点数和边数。每个节点用\([1,n]\)的整数编号。接下来的\(m\)行,每行包含两个整数:\(a, b, c\),表示节点\(a, b\)之间的边的权值为\(c\),其中\(1\leq c\leq 10^9\)。数据保证不会出现自回边和重边。
- 注意:具有相同权值的边不会超过\(10\)条。
- 输出格式
- 输出不同的最小生成树有多少个。你只需要输出数量对\(31011\)的模就可以了。
- 题解
- 引理
- 对于一张给定的无向图,权值相同的边在该无向图的最小生成树中数量相同,连接的集合相同(作用相同)
- 证明:(数学归纳法)
- 假设\(kruskal\)过程中当加入完一种权值的边,要加入下一种权值\(x\)的边时,对于权值为\(x\)的边所面对的集合相同。因为集合相同,故加入的边数相同。
- 假设加入完权值为\(x\)的边后集合为\(S\),边数为\(cnt\),权值为\(x\)的边加入到最小生成树的边集的集合为\(in_x\),没有加入边集的集合为\(out_x\)。
- 想要让\(S\)产生变化,有三种情况:
- 将 \(e\in in_x\) 从 \(in_x\) 中删除加入到\(out_x\)。
- 但根据\(kruskal\)的算法,如果 存在 \(e'\) 使得两个原本不交的点集合并成一个点集,则该边\(e'\)必然加入\(in_x\)。因此\(e\)不可能从\(in_x\)中删除。
- 将 \(e\in out_x\) 从 \(out_x\) 中删除加入到\(in_x\)中。
- 同理,如果该边能够加入\(in_x\),必然已经存在于\(in_x\)中了,因此\(\forall e\in out_x\)都不可能加入\(in_x\)
- 将 \(e_1\in in_x\) 从 \(in_x\) 中删除加入到\(out_x\)中,将\(e_2\in out_x\)从\(out_x\)中删除加入到\(in_x\)中。
- 假设如此操作能够改变\(S\)。设\(e_1\)原先连接的两个点集为\(S_{1,1},S_{1,2}\),\(e_2\)原先连接的两个点集为\(S_{2,1},S_{2,2}\)。
- 如果\(S_{1,1},S_{1,2}\)与\(S_{2,1},S_{2,2}\)不相同,\(e_2\)想要加入\(in_x\)中,必然要满足\(S_{2,1},S_{2,2}\)两个点集在原最小生成树上是两个不同的点集,而如果是这样的话,\(e_2\)必然已经被加入\(in_x\)中,而不可能存在于\(out_x\)中。
- 如果\(S_{1,1},S_{1,2}\)与\(S_{2,1},S_{2,2}\)相同,如此操作对\(S\)无影响。
- 将 \(e\in in_x\) 从 \(in_x\) 中删除加入到\(out_x\)。
- 因此对于权值\(x\)的边加入后的\(S'\),无论权值\(x\)的边如何取,一定全部相同
- 证毕
- 证明:(数学归纳法)
- 对于一张给定的无向图,权值相同的边在该无向图的最小生成树中数量相同,连接的集合相同(作用相同)
- 因此我们可以将每种不同权值的边独立开来考虑,这时就有两种想法:
- 先做一遍完整的\(kruskal\)求得每种权值的边在最小生成树出现的条数。再对于权值\(x\)的边我通过\(dfs\)求得所有合法加入方案,共\(cnt_x\)种。再用乘法原理的\(ans=\prod_{x}cnt_x\ \%mod\)
- 在做\(kruskal\)时对于权值\(x\)的边单独考虑,将加入权值\(x\)的边前的图缩点,连上所有权值\(x\)的边,跑\(matrix-tree\)定理,跑出所有生成树方案\(cnt_x\)。再用乘法原理的\(ans=\prod_{x}cnt_x\ \%mod\)
- 注意:将权值\(x\)的边连进\(matrix-tree\)的\(mat\)中后,这个缩过点的图不一定连通,要加一些桥边( 桥边 对 生成树方案个数 无贡献)保证图连通
- 引理
\(dfs\)版
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN=1e2+5;
const int MAXM=2e3+5;
const int mod=31011;
struct rec{
int u,v,w;
} ed[MAXM];
int cnt[MAXM];
int n,m,tot,ans,sum;
int fa[MAXN];
bool use[MAXM];
bool cmp(rec a,rec b){ return a.w<b.w; }
int find(int x){ return fa[x]==x?x:find(fa[x]); }
void uion(int x,int y){ fa[find(x)]=find(y); }
void dfs(int stp,int l,int r,int now,int cnt){
if (now==r+1){
// cout<<stp<<" "<<cnt<<endl;
if (stp==cnt) sum=(sum+1)%mod;
return;
}
int u=ed[now].u,v=ed[now].v;
int fu=find(u),fv=find(v);
if (fu!=fv){
fa[fu]=fv;
dfs(stp+1,l,r,now+1,cnt);
fa[fu]=fu; fa[fv]=fv;
}
dfs(stp,l,r,now+1,cnt);
}
int main(){
scanf("%d%d",&n,&m);
for (int i=1;i<=m;i++){
int u,v,w; scanf("%d%d%d",&u,&v,&w);
ed[i]=(rec){u,v,w};
}
for (int i=1;i<=n;i++) fa[i]=i;
sort(ed+1,ed+m+1,cmp);
int res=0;
for (int i=1;i<=m;i++){
if (ed[i].w!=ed[i-1].w) res++;
int u=ed[i].u,v=ed[i].v;
if (find(u)==find(v)) continue;
cnt[res]++; uion(u,v); tot++;
}
if (tot!=n-1) return printf("0\n"),0;
for (int i=1;i<=n;i++) fa[i]=i;
ans=res=1;
for (int l=1,r=1;l<=m;l=r+1,r++,res++){
while (ed[l].w==ed[r].w) r++;
r--; sum=0;
dfs(0,l,r,l,cnt[res]);
// cout<<l<<" "<<r<<" "<<cnt[res]<<" "<<sum<<endl;
ans=ans*sum%mod;
for (int i=l;i<=r;i++){
int u=ed[i].u,v=ed[i].v;
if (find(u)==find(v)) continue;
uion(u,v);
}
}
printf("%d\n",ans);
return 0;
}
\(matrix-tree\)版
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN=1e2+5;
const int MAXM=1e3+5;
const int mod=31011;
struct rec{
int u,v,w;
} ed[MAXM];
int n,m,ans=1,tot;
int fa[MAXN];
int use[MAXN];
int mat[MAXN][MAXN];
int ffa[MAXN];
bool cmp(rec a,rec b){ return a.w<b.w; }
int find(int x){ return fa[x]==x?x:fa[x]=find(fa[x]); }
void uion(int x,int y){ fa[find(x)]=find(y); }
int ffind(int x){ return ffa[x]==x?x:ffa[x]=ffind(ffa[x]); }
void fuion(int x,int y){ ffa[ffind(x)]=ffind(y); }
int main(){
scanf("%d%d",&n,&m);
for (int i=1;i<=m;i++){
int u,v,w; scanf("%d%d%d",&u,&v,&w);
ed[i]=(rec){u,v,w};
}
sort(ed+1,ed+m+1,cmp);
for (int i=1;i<=n;i++) fa[i]=i;
for (int l=1,r=1;l<=m;l=r+1,r++){
while (ed[l].w==ed[r].w) r++;
r--;
// cout<<l<<" "<<r<<endl;
memset(mat,0,sizeof(mat));
memset(use,0,sizeof(use));
bool flag=0;
int cnt=0;
for (int i=l;i<=r;i++){
int u=ed[i].u,v=ed[i].v;
int fu=find(u),fv=find(v);
if (!use[fu]) use[fu]=++cnt;
if (!use[fv]) use[fv]=++cnt;
}
for (int i=1;i<=cnt;i++) ffa[i]=i;
for (int i=l;i<=r;i++){
int u=ed[i].u,v=ed[i].v;
int fu=find(u),fv=find(v);
if (fu!=fv) flag=1;
fu=use[fu]; fv=use[fv];
fuion(fu,fv);
mat[fu][fv]--; mat[fv][fu]--;
mat[fu][fu]++; mat[fv][fv]++;
}
for (int i=2;i<=cnt;i++){
int fu=ffind(i-1),fv=ffind(i);
if (fu!=fv){
fuion(i-1,i);
mat[fu][fv]--; mat[fv][fu]--;
mat[fu][fu]++; mat[fv][fv]++;
}
}
cnt--;
/* for (int i=1;i<=cnt;i++){
for (int j=1;j<=cnt;j++) cout<<mat[i][j]<<" ";
cout<<endl;
}
cout<<endl;
*/ int ret=1;
for (int i=1;i<=cnt;i++){
int pos=i;
for (int j=i+1;j<=cnt;j++){
if (mat[j][i]) pos=j;
}
for (int j=1;j<=cnt;j++) swap(mat[i][j],mat[pos][j]);
if (pos!=i) ret=-ret;
for (int j=i+1;j<=cnt;j++){
while (mat[j][i]){
int t=mat[j][i]/mat[i][i];
for (int k=1;k<=cnt;k++){
mat[j][k]=(mat[j][k]-t*mat[i][k])%mod;
}
if (!mat[j][i]) break;
ret=-ret;
for (int k=1;k<=cnt;k++) swap(mat[j][k],mat[i][k]);
}
}
ret=ret*mat[i][i]%mod;
}
/* for (int i=1;i<=cnt;i++){
for (int j=1;j<=cnt;j++) cout<<mat[i][j]<<" ";
cout<<endl;
}
*/ ret=(ret%mod+mod)%mod;
// cout<<ret<<endl;
if (flag) ans=ans*ret%mod;
for (int i=l;i<=r;i++){
int u=ed[i].u,v=ed[i].v;
int fu=find(u),fv=find(v);
if (fu==fv) continue;
uion(u,v); tot++;
}
}
if (tot!=n-1) return printf("0\n"),0;
printf("%d\n",ans);
return 0;
}
随机推荐
- PreparedStatement的setDate方法如何设置日期
pstmt.setString(12, "to_char(sysdate,'YYYY-MM-DD HH24:MI:SS')");这样写不对,应该如何写 该方法用于将指定的参数设置为 ...
- lnmp一键安装包 配置多站点
在/usr/local/nginx/conf/vhost下配置多站点的文件,一个站点对应一个文件,配置如下信息: vim ./vhost/test.conf server { listen ; ser ...
- Gym 101190H Hard Refactoring (模拟坑题)
题意:给定 n 个区间,让你进行合并,问你最后的区间是,如果是空集,输出 false 如果区间是是 [-32768,32767] ,则是true. 析:进行区间合并,要注意,如果是 x >= 0 ...
- 安装并使用PICT,生成测试用例
一.PICT简介 PICT工具是在微软公司内部使用的一款承兑组合的命令行生成工具,现在已经对外提供,可以在 http://download.microsoft.com/download/f/5/5/f ...
- 双系统Grub引导下恢复windows引导的方法
此方法适用于windows系统正常,linux和windows 双系统下恢复windows系统引导.需要使用windows安装u盘 1. 启动至windows安装u盘,点击修复计算机 2. 进入命令行 ...
- Update语句到底是如何操作记录的?
经常会听到一些开发的朋友说,Update语句的操作原理是:先删后加!今天偶然想起这句话,索性验证一下.参考下面示例: USE CSDN go --新添加一个文件组和文件 ALTER DATABASE ...
- ecahrt 扇形(半扇形)
var data = [{ "name": "1", "value": 54 }, { "name": "2& ...
- Linq to SQL 中将数字转换为字符串
使用LINQ to Entities中的SqlFunctions调用数据库中的函数 添加引用System.Data.Entity 引用命名空间 using System.Data.Objects.Sq ...
- WPF 实现INotifyPropertyChanged .Net Framework 4.5
自己动手写了一个基类来实现INotifyPropertyChanged接口,以后可以直接使用. using System.ComponentModel; using System.Runtime.Co ...
- windows系统重装流程
新电脑或者电脑因系统文件损坏都需要重装系统,因为之前工作中有一段时间经常帮同事装系统,总结了一些经验,现分享给大家. 重装系统大体有下列几种种常见方法: 1. 系统重装盘: 2. 从U盘重装: 3. ...