BZOJ 1016 [JSOI2008]最小生成树计数 ——Matrix-Tree定理

考虑从小往大加边,然后把所有联通块的生成树个数计算出来。

然后把他们缩成一个点,继续添加下一组。

最后乘法原理即可。

写起来很恶心

#include <queue>
#include <cmath>
#include <vector>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;

#define F(i,j,k) for (int i=j;i<=k;++i)
#define D(i,j,k) for (int i=j;i>=k;--i)
#define maxn 1005
#define eps 1e-6
const int md=31011;
vector <int> v,to[maxn];
queue <int> q;
struct Edge{int u,v,w;}a[maxn];
int n,m,fa[maxn];
int b[maxn][maxn],inv[maxn];
int vcnt,du[maxn],list[maxn],vis[maxn];
bool cmp(Edge x,Edge y)
{return x.w<y.w;}
int gf(int k)
{if (fa[k]==k) return k; else return fa[k]=gf(fa[k]);}
int gauss(int n)
{
    F(i,1,n) F(j,1,n) b[i][j]%=md;
    int ret=1;
    for (int i=1;i<n;++i)
    {
        for (int j=i+1;j<n;++j)
            while (b[j][i])
            {
                int t=b[i][i]/b[j][i];
                for (int k=i;k<n;++k)
                    b[i][k]=(b[i][k]-b[j][k]*t+md)%md;
                for (int k=i;k<n;++k)
                    swap(b[i][k],b[j][k]);
                ret=-ret;
            }
        if (b[i][i]==0) return 0;
        ret=ret*b[i][i]%md;
    }
    return abs((ret+md)%md);
}
int main()
{
    scanf("%d%d",&n,&m);
    F(i,1,n) fa[i]=i;
    F(i,1,m){scanf("%d%d%d",&a[i].u,&a[i].v,&a[i].w);}
    sort(a+1,a+m+1,cmp);
    int now=1,ans=1;
    while (now<=m)
    {
        int l=now,r=now;
        vcnt=0;
        memset(du,0,sizeof du);
        F(i,1,n) to[i].clear();
        while (a[r+1].w==a[r].w) r++;
        now=r+1;
        F(i,l,r)
        {
            int fl=gf(a[i].u),fr=gf(a[i].v);
            to[fl].push_back(fr);
            to[fr].push_back(fl);
            if (fl!=fr) du[fl]++,du[fr]++;
        }
        memset(vis,0,sizeof vis);
        F(i,1,n)
            if (du[i]&&!vis[i])
            {
                v.clear();
                memset(b,0,sizeof b);
                memset(inv,0,sizeof inv);
                q.push(i);inv[i]=1;vis[i]=1;
                while (!q.empty())
                {
                    int x=q.front();v.push_back(x);q.pop();
                    for (int j=0;j<to[x].size();++j)
                        if (!vis[to[x][j]])
                            q.push(to[x][j]),inv[to[x][j]]=1,vis[to[x][j]]=1;
                }
                for (int j=0;j<v.size();++j) list[v[j]]=j+1;
                for (int j=0;j<v.size();++j)
                    for (int k=0;k<to[v[j]].size();++k)
                        if (inv[to[v[j]][k]])
                        {
                            b[list[v[j]]][list[v[j]]]++,b[list[v[j]]][list[to[v[j]][k]]]--;
                        }
                ans*=gauss(v.size());
                ans%=md;
            }
        F(i,l,r)
        {
            int fl=gf(a[i].u),fr=gf(a[i].v);
            if (fl!=fr){fa[fl]=fr;}
        }
    }
    int cnt=0;
    F(i,1,n) if (fa[i]==i)
    {
        cnt++;
        if (cnt==2) {printf("0\n"); return 0;}
    }
    printf("%d\n",ans);
}

  

时间: 2024-08-05 02:45:35

BZOJ 1016 [JSOI2008]最小生成树计数 ——Matrix-Tree定理的相关文章

[BZOJ 1016] [JSOI2008] 最小生成树计数 【DFS】

题目链接:BZOJ - 1016 题目分析 最小生成树的两个性质: 同一个图的最小生成树,满足: 1)同一种权值的边的个数相等 2)用Kruscal按照从小到大,处理完某一种权值的所有边后,图的连通性相等 这样,先做一次Kruscal求出每种权值的边的条数,再按照权值从小到大,对每种边进行 DFS, 求出这种权值的边有几种选法. 最后根据乘法原理将各种边的选法数乘起来就可以了. 特别注意:在DFS中为了在向下DFS之后消除决策影响,恢复f[]数组之前的状态,在DFS中调用的Find()函数不能路

BZOJ 1016: [JSOI2008]最小生成树计数

http://www.lydsy.com/JudgeOnline/problem.php?id=1016 题意: 思路: 一个无向图所有的最小生成树中某种权值的边的数目均相同. 引用一篇大牛的证明: 我们证明以下定理:一个无向图所有的最小生成树中某种权值的边的数目均相同. 开始时,每个点单独构成一个集合. 首先只考虑权值最小的边,将它们全部添加进图中,并去掉环,由于是全部尝试添加,那么只要是用这种权值的边能够连通的点,最终就一定能在一个集合中. 那么不管添加的是哪些边,最终形成的集合数都是一定的

BZOJ 1016 JSOI2008 最小生成树计数 Kruskal

题目大意:给定一个无向图,求最小生成树的方案数 首先对于一个无向图的最小生成树,每种边权的边的数量是一定的 首先我们先跑一遍Kruskal,求出最小生成树上每种边权的出现次数 然后对于每种出现在最小生成树上的边权,我们从小到大处理 对于每种边权,我们枚举这种边权的边有多少种方案可以加进最小生成树上而不形成环 这个用状压处理 ans乘上这个值 然后把这种边权连接的所有联通块缩点 注意最小生成树不存在时输出0 #include<map> #include<cstdio> #includ

bzoj 1016: [JSOI2008]最小生成树计数【dfs+克鲁斯卡尔】

有一个性质就是组成最小生成树总边权值的若干边权总是相等的 这意味着按边权排序后在权值相同的一段区间内的边能被选入最小生成树的条数是固定的 所以先随便求一个最小生成树,把每段的入选边数记录下来 然后对于每一段dfs找合法方案即可,注意dfs中需要退回并查集,所以用不路径压缩的并查集 然后根据乘法定理,把每一段dfs后的结果乘起来即可. #include<iostream> #include<cstdio> #include<algorithm> using namespa

BZOJ 题目1016: [JSOI2008]最小生成树计数(Kruskal+Matrix_Tree)

1016: [JSOI2008]最小生成树计数 Time Limit: 1 Sec  Memory Limit: 162 MB Submit: 3569  Solved: 1425 [Submit][Status][Discuss] Description 现在给出了一个简单无向加权图.你不满足于求出这个图的最小生成树,而希望知道这个图中有多少个不同的最小生成树.(如果两颗最小生成树中至少有一条边不同,则这两个最小生成树就是不同的).由于不同的最小生成树可能很多,所以你只需要输出方案数对3101

1016: [JSOI2008]最小生成树计数

1016: [JSOI2008]最小生成树计数 Time Limit: 1 Sec  Memory Limit: 162 MBSubmit: 6200  Solved: 2518[Submit][Status][Discuss] Description 现在给出了一个简单无向加权图.你不满足于求出这个图的最小生成树,而希望知道这个图中有多少个不同的 最小生成树.(如果两颗最小生成树中至少有一条边不同,则这两个最小生成树就是不同的).由于不同的最小生 成树可能很多,所以你只需要输出方案数对3101

【BZOJ】1016: [JSOI2008]最小生成树计数 深搜+并查集

最小生成树计数 Description 现在给出了一个简单无向加权图.你不满足于求出这个图的最小生成树,而希望知道这个图中有多少个不同的最小生成树.(如果两颗最小生成树中至少有一条边不同,则这两个最小生成树就是不同的).由于不同的最小生成树 可能很多,所以你只需要输出方案数对31011的模就可以了. Input 第 一行包含两个数,n和m,其中1<=n<=100; 1<=m<=1000; 表示该无向图的节点数和边数.每个节点用1~n的整数编号.接下来的m行,每行包含两个整数:a,

【BZOJ】1016: [JSOI2008]最小生成树计数(kruskal+特殊的技巧)

http://www.lydsy.com/JudgeOnline/problem.php?id=1016 想也想不到QAQ 首先想不到的是:题目有说,具有相同权值的边不会超过10条. 其次:老是去想组合计数怎么搞.......于是最sb的暴力都不会了.. 所以这题暴力搞就行了orz 依次加边,每一种边的方案数乘起来就是方案了. 注意并查集不能路径压缩,否则在计数的时候会waQAQ因为并查集的路径压缩是不可逆的QAQ #include <cstdio> #include <cstring&

bzoj1016: [JSOI2008]最小生成树计数(kruskal+dfs)

1016: [JSOI2008]最小生成树计数 题目:传送门 题解: 神题神题%%% 据说最小生成树有两个神奇的定理: 1.权值相等的边在不同方案数中边数相等  就是说如果一种方案中权值为1的边有n条    那么在另一种方案中权值为1的边也一定有n条 2.如果边权为1的边连接的点是x1,x2,x3   那么另一种方案中边权为1的边连接的也一定是x1,x2,x3  如果知道了这两条定理那就很好做了啊: 因为等权边的条数一定,那么我们就可以预处理求出不同边权的边的条数 题目很人道的保证了边权相同的边