1. 程式人生 > >洛谷4208 JSOI2008最小生成樹計數(矩陣樹定理+高斯消元)

洛谷4208 JSOI2008最小生成樹計數(矩陣樹定理+高斯消元)

order code ace 才會 red sin 出現 span 進行

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最小生成樹計數(矩陣樹定理+高斯消元)