洛谷4208 JSOI2008最小生成树计数(矩阵树定理+高斯消元)

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;
}

猜你喜欢

转载自www.cnblogs.com/yimmortal/p/10158855.html
今日推荐