BZOJ-1016&洛谷P4208最小生成树计数-【JSOI2008】dfs|矩阵树&缩点+最小生成树

Time Limit: 1 Sec  Memory Limit: 162 MB

洛谷:时间限制1.00s  内存限制125.00MB
题目链接:https://www.lydsy.com/JudgeOnline/problem.php?id=1016
洛谷:https://www.luogu.com.cn/problem/P4208

Description

  现在给出了一个简单无向加权图。你不满足于求出这个图的最小生成树,而希望知道这个图中有多少个不同的最小生成树。(如果两颗最小生成树中至少有一条边不同,则这两个最小生成树就是不同的)。由于不同的最小生成树可能很多,所以你只需要输出方案数对31011的模就可以了。

Input

  第一行包含两个数,n和m,其中1<=n<=100; 1<=m<=1000; 表示该无向图的节点数和边数。每个节点用1~n的整数编号。接下来的m行,每行包含两个整数:a, b, c,表示节点a, b之间的边的权值为c,其中1<=c<=1,000,000,000。数据保证不会出现自回边和重边。注意:具有相同权值的边不会超过10条。

Output

  输出不同的最小生成树有多少个。你只需要输出数量对31011的模就可以了。

Sample Input

4 6
1 2 1
1 3 1
1 4 1
2 3 2
2 4 1
3 4 1

Sample Output

8

最小生成树计数。。。开始有点蒙,我们在此之前需要注意到MST的一个性质: 每种权值的边的数量是固定的。
我们先跑一次MST,记录每种边所需要的数量。
通过题目的条件我们也可以看见,重复边并不多,那么假设长度为m的边使用了c次,那么我们可以进行dfs搜索对最小生成树中每一种长度的边进行搜索观察是否能构成一棵树:
//dfs(l[i],i,0)
void dfs(int now,int x,int num)
{
    if (now>r[x]){//从l[i]搜索到r[i],判断第i个长度的边是否用了c[cnt]个
        sum+=(num==c[x]);
        return;
    }
    int fa=find(eg[now].u),fb=find(eg[now].v);
    if (fa!=fb){
        father[fa]=fb;
        dfs(now+1,x,num+1);
        father[fa]=fa;father[fb]=fb;
    }
    dfs(now+1,x,num);
}

那么我们算出来的sum就是当前可以构成的MST的数量,然后每一种边我们都跑一遍,利用乘法原理乘起来即可。

以下是AC代码(枚举dfs+MST):

#include 
#include 
#include 
using namespace std;

const int mod=31011;
const int mac=1e3+10;

struct node
{
    int u,v,w;
    bool operator <(const node &a)const{
        return w<a.w;
    }
}eg[mac<<1];

int father[mac],l[mac],r[mac],c[mac],cnt,sum;

int find(int x){return x==father[x]?x:find(father[x]);}
//dfs(l[i],i,0)
void dfs(int now,int x,int num)
{
    if (now>r[x]){//从l[i]搜索到r[i],判断第i个长度的边是否用了c[cnt]个
        sum+=(num==c[x]);
        return;
    }
    int fa=find(eg[now].u),fb=find(eg[now].v);
    if (fa!=fb){
        father[fa]=fb;
        dfs(now+1,x,num+1);
        father[fa]=fa;father[fb]=fb;
    }
    dfs(now+1,x,num);
}

int main()
{
    //freopen("in.txt","r",stdin);
    int n,m;
    scanf ("%d%d",&n,&m);
    for (int i=1; i<=m; i++){
        int u,v,w;
        scanf("%d%d%d",&u,&v,&w);
        eg[i]=node{u,v,w};
    }
    sort(eg+1,eg+1+m);
    for (int i=1; i<=n; i++) father[i]=i;
    int tot=0;
    for (int i=1; i<=m; i++){
        if (eg[i].w!=eg[i-1].w) r[cnt]=i-1,l[++cnt]=i;
        int fa=find(eg[i].u),fb=find(eg[i].v);
        if (fa!=fb) ++c[cnt],father[fa]=fb,++tot;//c[cnt]用了第cnt边长度几次
    }
    r[cnt]=m;
    if (tot!=n-1) {printf("0\n");return 0;}
    for (int i=1; i<=n; i++) father[i]=i;
    int ans=1;
    for (int i=1; i<=cnt; i++){
        sum=0;
        dfs(l[i],i,0);
        ans=ans*sum%mod;
        for (int j=l[i]; j<=r[i]; j++){
            int fa=find(eg[j].u),fb=find(eg[j].v);
            if (fa!=fb) father[fa]=fb;
        }
    } 
    printf("%d\n",ans);
    return 0;
}
View Code

接下来就是利用矩阵树的做法了,我们都知道矩阵树可以直接计算一个图能够生成多少棵树,但怎么使得这棵树最小呢?其实和上面的做法差不多,我们枚举已获得的最小生成树的每一种边权,然后将非该种边权的边全部缩点,然后剩下的就可以直接使用矩阵树了,当然这里也是利用乘法原理,对每一种边权算出来的结果相乘。

 以下是AC代码(缩点+矩阵树+MST):

#include 
#include 
#include 
using namespace std;

#define debug() printf("+++\n")

const int mac=1e3+10;
const int mod=31011;

struct node
{
    int u,v,w;
    bool operator <(const node &a)const{
        return w<a.w;
    }
}eg[mac<<1],tr[mac<<1];
int tot,father[mac],mat[110][110];
int val[mac],id[mac];

int find(int x){return x==father[x]?x:father[x]=find(father[x]);}

int kurskal(int m,int n)
{
    int cnt=0;
    for (int i=1; i<=n; i++) father[i]=i;
    sort(eg+1,eg+1+m);
    for (int i=1; i<=m; i++){
        int fa=find(eg[i].u),fb=find(eg[i].v);
        if (fa!=fb) {
            father[fa]=fb;
            tr[++cnt]=eg[i];
            if (eg[i].w!=val[tot]) val[++tot]=eg[i].w;//统计MST中总共的边是种数
        }
    }
    return cnt==n-1?1:0;
}

int suodian(int x,int n)//MST中的非x权边缩点
{
    for (int i=1; i)
        if (tr[i].w!=x) {
            int fa=find(tr[i].u),fb=find(tr[i].v);
            if (fa!=fb) father[fa]=fb;
        }
    int block=0;
    for (int i=1; i<=n; i++)
        if (father[i]==i) id[i]=++block;//计算团的数量
    for (int i=1; i<=n; i++)
        id[i]=id[find(i)];//一个团中的点编号,!注意是find(i)不是father[i]
    return block;
}

int gauss(int n)//高斯消元
{
    int ans=1;
    for (int i=1; i<=n; i++){
        for (int j=i+1; j<=n; j++){
            while (mat[j][i]){
                int d=mat[i][i]/mat[j][i];
                for (int k=i; k<=n; k++)
                    mat[i][k]=((mat[i][k]%mod)-(mat[j][k]*d%mod)+mod)%mod;
                swap(mat[i],mat[j]);
                ans=-ans;
            }
        }
        ans=ans*mat[i][i]%mod;
        ans=(ans+mod)%mod;
    }
    return ans;
}

void add(int u,int v)
{
    //du[u][u]++;du[v][v]++;g[u][v]++,g[v][u]++;
    //mat[u][v]=du[u][v]-g[u][v];度,邻接
    mat[u][v]--;mat[v][u]--;
    mat[u][u]++;mat[v][v]++;
}

void rebuild(int x,int m)//重新构造基尔霍夫矩阵
{
    memset(mat,0,sizeof mat);
    for (int i=1; i<=m; i++)
        if (eg[i].w==x)
            add(id[eg[i].u],id[eg[i].v]);
}

int main(int argc, char const *argv[])
{
    int n,m;
    scanf ("%d%d",&n,&m);
    for (int i=1; i<=m; i++){
        int u,v,w;
        scanf("%d%d%d",&u,&v,&w);
        eg[i]=node{u,v,w};
    }
    if (!kurskal(m,n)) {printf("0\n");return 0;}
    int ans=1;
    for (int i=1; i<=tot; i++){
        for (int j=1; j<=n; j++) father[j]=j;
        int num=suodian(val[i],n);
        rebuild(val[i],m);
        ans=ans*gauss(num-1)%mod;
    }
    printf("%d\n",ans);
    return 0;
}

 

你可能感兴趣的:(BZOJ-1016&洛谷P4208最小生成树计数-【JSOI2008】dfs|矩阵树&缩点+最小生成树)