[矩阵树定理 模板题] BZOJ 1016 [JSOI2008]最小生成树计数 & HDU 4408 Minimum Spanning Tree

根据Kruscal算法的过程 我们可以得到一些结论
来自 这里

  • 如果 A,B 同为 G 的最小生成树,且 A 的边权从小到大为 w(a1),w(a2),w(a3),w(an) B 的边权从小到大为 w(b1),w(b2),w(b3),w(bn),则有 w(ai)=w(bi)
  • 如果 A,B 同为 G 的最小生成树,如果 A,B 都从零开始从小到大加边( A A 的边, B B 的边)的话,每种权值加完后图的联通性相同。
  • 如果在最小生成树 A 中权值为 v 的边有 k 条,用任意 k 条权值为 v 的边替换 A 中的权为 v <script type="math/tex" id="MathJax-Element-21">v</script> 的边且不产生环的方案都是一棵合法最小生成树。

然后就可以干些奇奇怪怪的事情 按边权分阶段 然后这些边会把某些连通块缩成一个大连通块 这些大连通块之间互不影响 只要对这些大连通块分别做矩阵树定理求余子式 用乘法原理就好了

可能还是这里讲的比较清楚啊

#include<cstdio>
#include<cstdlib>
#include<algorithm>
using namespace std;

inline char nc(){
  static char buf[100000],*p1=buf,*p2=buf;
  return p1==p2&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++;
}
inline void read(int &x){
  char c=nc(),b=1;
  for (;!(c>='0' && c<='9');c=nc()) if (c=='-') b=-1;
  for (x=0;c>='0' && c<='9';x=x*10+c-'0',c=nc()); x*=b;
}

const int N=105;
const int M=1005;
const int P=31011;

struct Tset{
  int fat[N];
  void init(int n){ for (int i=1;i<=n;i++) fat[i]=i; }
  int Fat(int u){ return u==fat[u]?u:fat[u]=Fat(fat[u]); }
}S1,S2;

struct edge{
  int u,v,w;
  bool operator < (const edge &B) const{ return w<B.w; }
}ed[M];

int n,m,ans=1;
int A[N][N];
int sta[N][N],top[N];
int a[N][N];

inline int det(int n){
  int ret=1;
  for (int i=1;i<=n;i++) for (int j=1;j<=n;j++) a[i][j]=(a[i][j]%P+P)%P;
  for (int i=1;i<=n;i++){
    for (int j=i+1;j<=n;j++)
      while (a[j][i]){
    int t=a[i][i]/a[j][i];
    for (int k=i;k<=n;k++) a[i][k]=(a[i][k]+P-a[j][k]*t%P)%P;
    for (int k=i;k<=n;k++) swap(a[i][k],a[j][k]);
    ret=P-ret;
      }
    if (!a[i][i]) return 0;
    ret=ret*a[i][i]%P;
  }
  return ret;
}

int main(){
  int last=0;
  freopen("t.in","r",stdin);
  freopen("t.out","w",stdout);
  read(n); read(m); if (m<n-1) return printf("0\n"),0; 
  for (int i=1;i<=m;i++) read(ed[i].u),read(ed[i].v),read(ed[i].w);
  sort(ed+1,ed+m+1); S1.init(n); S2.init(n);
  for (int i=1;i<=m+1;i++){
    if ((i>1 && ed[i].w!=ed[i-1].w) || i==m+1){
      for (int j=last;j<i;j++)
    A[S2.Fat(ed[j].u)][S2.Fat(ed[j].v)]++,A[S2.Fat(ed[j].v)][S2.Fat(ed[j].u)]++;
      for (int j=1;j<=n;j++)
    if (S2.Fat(j)==j)
      sta[S1.Fat(j)][++top[S1.Fat(j)]]=j;
      for (int j=1;j<=n;j++)
    if (top[j]>1){
      for (int k=1;k<=top[j];k++) a[k][k]=0;
      for (int k=1;k<=top[j];k++)
        for (int l=k+1;l<=top[j];l++){
          int x=sta[j][k],y=sta[j][l];
          a[k][l]=-A[x][y]; a[l][k]=-A[x][y];
          a[k][k]+=A[x][y]; a[l][l]+=A[x][y];
        }
      (ans*=det(top[j]-1))%=P;
    }
      for (int j=last;j<i;j++)
    A[S2.Fat(ed[j].u)][S2.Fat(ed[j].v)]--,A[S2.Fat(ed[j].v)][S2.Fat(ed[j].u)]--;
      for (int j=1;j<=n;j++)
    S2.fat[j]=S1.Fat(j),top[j]=0;
      last=i;
    }
    int x=S1.Fat(ed[i].u),y=S1.Fat(ed[i].v);
    if (x==y) continue;
    S1.fat[x]=y;
  }
  int cnt=0;
  for (int i=1;i<=n;i++)
    cnt+=S1.Fat(i)==i;
  printf("%d\n",cnt==1?ans:0);
  return 0;
}
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cstring>
#define cl(x) memset(x,0,sizeof(x))
using namespace std;
typedef long long ll;

inline char nc(){
  static char buf[100000],*p1=buf,*p2=buf;
  return p1==p2&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++;
}
inline void read(int &x){
  char c=nc(),b=1;
  for (;!(c>='0' && c<='9');c=nc()) if (c=='-') b=-1;
  for (x=0;c>='0' && c<='9';x=x*10+c-'0',c=nc()); x*=b;
}

const int N=105;
const int M=1005;

struct Tset{
  int fat[N];
  void init(int n){ for (int i=1;i<=n;i++) fat[i]=i; }
  int Fat(int u){ return u==fat[u]?u:fat[u]=Fat(fat[u]); }
}S1,S2;

struct edge{
  int u,v,w;
  bool operator < (const edge &B) const{ return w<B.w; }
}ed[M];

int n,m,ans,P;
int A[N][N];
int sta[N][N],top[N];
int a[N][N];

inline int det(int n){
  int ret=1;
  for (int i=1;i<=n;i++) for (int j=1;j<=n;j++) a[i][j]=(a[i][j]%P+P)%P;
  for (int i=1;i<=n;i++){
    for (int j=i+1;j<=n;j++)
      while (a[j][i]){
    int t=a[i][i]/a[j][i];
    for (int k=i;k<=n;k++) a[i][k]=(a[i][k]+P-(ll)a[j][k]*t%P)%P;
    for (int k=i;k<=n;k++) swap(a[i][k],a[j][k]);
    ret=P-ret;
      }
    if (!a[i][i]) return 0;
    ret=(ll)ret*a[i][i]%P;
  }
  return ret;
}

int main(){
  int last;
  freopen("t.in","r",stdin);
  freopen("t.out","w",stdout);
  while (1){
    read(n); read(m); read(P); if (!n && !m && !P) break;
    ans=1; last=0;
    for (int i=1;i<=m;i++) read(ed[i].u),read(ed[i].v),read(ed[i].w);
    if (m<n-1) { printf("0\n"); continue; } 
    sort(ed+1,ed+m+1); S1.init(n); S2.init(n);
    for (int i=1;i<=m+1;i++){
      if ((i>1 && ed[i].w!=ed[i-1].w) || i==m+1){
    for (int j=last;j<i;j++)
      A[S2.Fat(ed[j].u)][S2.Fat(ed[j].v)]++,A[S2.Fat(ed[j].v)][S2.Fat(ed[j].u)]++;
    for (int j=1;j<=n;j++)
      if (S2.Fat(j)==j)
        sta[S1.Fat(j)][++top[S1.Fat(j)]]=j;
    for (int j=1;j<=n;j++)
      if (top[j]>1){
        for (int k=1;k<=top[j];k++) a[k][k]=0;
        for (int k=1;k<=top[j];k++)
          for (int l=k+1;l<=top[j];l++){
        int x=sta[j][k],y=sta[j][l];
        a[k][l]=-A[x][y]; a[l][k]=-A[x][y];
        a[k][k]+=A[x][y]; a[l][l]+=A[x][y];
          }
        ans=(ll)ans*det(top[j]-1)%P;
      }
    for (int j=last;j<i;j++)
      A[S2.Fat(ed[j].u)][S2.Fat(ed[j].v)]--,A[S2.Fat(ed[j].v)][S2.Fat(ed[j].u)]--;
    for (int j=1;j<=n;j++)
      S2.fat[j]=S1.Fat(j),top[j]=0;
    last=i;
      }
      int x=S1.Fat(ed[i].u),y=S1.Fat(ed[i].v);
      if (x==y) continue;
      S1.fat[x]=y;
    }
    int cnt=0;
    for (int i=1;i<=n;i++) cnt+=S1.Fat(i)==i;
    printf("%d\n",cnt==1?ans%P:0);
  }
  return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值