• luogu 4208 [JSOI2008]最小生成树计数


    题目描述

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

    输入格式

    第一行包含两个数,n和m,其中1<=n<=100; 1<=m<=1000; 表示该无向图的节点数和边数。每个节点用1~n的整数编号。

    接下来的m行,每行包含两个整数:a, b, c,表示节点a, b之间的边的权值为c,其中1<=c<=1,000,000,000。

    数据保证不会出现自回边和重边。注意:具有相同权值的边不会超过10条。

    输出格式

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

    输入输出样例

    输入 #1
    4 6
    1 2 1
    1 3 1
    1 4 1
    2 3 2
    2 4 1
    3 4 1
    输出 #1
    8
    

    说明/提示

    说明 1<=n<=100; 1<=m<=1000;1ci109

    分析

    很妙的一道暴搜(?)题

    是的,暴搜,真是一个优秀的算法

    正解什么矩阵树,高斯消元啥的

    我暴搜流派就是流弊


    先放几个写得不错的题解:

    题解1hzwer大佬

    题解2luogu题解

    题解3神奇的证明

    怎么搜,首先一个结论:

    对于最小生成树的一个替代边,必有替代边等于最小生成树中的一边,并与这一边连接了同两个连通块

    所以,对于最小生成树的不同方案,相同长度的边的个数是一样的

    我们可以对相同长度的边进行暴搜(选/不选),最后判断是否与最小生成树中的个数相同来判断是否合法‘’

    用乘法原理,将各个不同长度的边的方案相乘得到最终方案数

      1 /**************************
      2 User:Mandy.H.Y
      3 Language:c++
      4 Problem:luogu
      5 Algorithm: 
      6 **************************/ 
      7 #include<bits/stdc++.h>
      8 
      9 using namespace std;
     10 
     11 const int maxn = 105;
     12 const int maxm = 1005;
     13 const int mod = 31011;
     14 
     15 int n,m,size,tot;
     16 int father[maxn];
     17 int cnt,ans,sum;
     18 int son[maxn];
     19 
     20 struct Node{
     21     int l,r,num;
     22 }a[maxm];
     23 
     24 struct Edge{
     25     int u,v,w;
     26 }edge[maxm];
     27 
     28 template<class T>inline void read(T &x){
     29     x = 0;char ch = getchar();bool flag = 0;
     30     while(!isdigit(ch)) flag |= ch == '-',ch = getchar();
     31     while(isdigit(ch)) x = (x << 1) + (x << 3) + (ch ^ 48),ch = getchar();
     32     if(flag) x = -x;
     33 }
     34 
     35 void file(){
     36     freopen("award10.in","r",stdin);
     37 }
     38 
     39 void eadd(int u,int v,int w){
     40     edge[++size].v = v;
     41     edge[size].u = u;
     42     edge[size].w = w;
     43 }
     44 
     45 bool cmp(const Edge &a,const Edge &b){
     46     return a.w < b.w;
     47 }
     48 
     49 void readdata(){
     50     read(n);read(m);
     51     for(int i = 1;i <= m; ++ i){
     52         int u,v,w;
     53         read(u);read(v);read(w);
     54         eadd(u,v,w);
     55     }
     56     sort(edge + 1,edge + m + 1,cmp);
     57 }
     58 
     59 int find1(int x){//压缩路径 
     60     return father[x] == x ? x : father[x] = find1(father[x]);
     61 }
     62 
     63 int find2(int x){//不压缩路径 
     64     return father[x] == x ? x : find2(father[x]);
     65 }
     66 
     67 void merge(int x,int y){
     68     father[find1(x)] = find1(y);
     69 }
     70 
     71 void merge1(int x,int y){
     72     if(son[x] > son[y]){
     73         father[y] = x;
     74         son[x] += son[y];
     75         son[y] = son[x];
     76     } else {
     77         father[x] = y;
     78         son[y] += son[x];
     79         son[x] = son[y];
     80     }
     81 }//按秩合并 
     82 
     83 void dfs(int id,int pos,int num){
     84     //id - 边的长度的编号
     85     //pos - 边的编号
     86     //num - 这条边在最小生成树中的个数 
     87     if(pos == a[id].r + 1){//到边了 
     88         if(num == a[id].num) sum++;
     89         return;//如果不等于在最小生成树中的个数,说明方案错了 
     90     }
     91     int u = find2(edge[pos].u);
     92     int v = find2(edge[pos].v);//不能压缩路径 
     93     if(u != v){
     94         father[u] = v;
     95         dfs(id,pos+1,num +1);//选这条边 
     96         father[u] = u;
     97     }
     98     dfs(id,pos + 1,num);//不选这条边 
     99 }
    100 
    101 void work(){
    102     edge[0].w = -10000;
    103     edge[m+1].w = -10000;
    104     for(int i = 1;i <= n; ++ i) father[i] = i;
    105     for(int i = 1;i <= m; ++ i){
    106         int u = find1(edge[i].u);
    107         int v = find1(edge[i].v);
    108         if(edge[i-1].w != edge[i].w){
    109             a[cnt].r = i-1;
    110             a[++cnt].l = i;//a数组中存下相同长度的边的左右边界 
    111         }
    112         if(u != v){
    113             a[cnt].num++;//存下这个长度的边在最小生成树中的个数 
    114             merge(u,v);
    115             ++tot;
    116         }
    117     }
    118     a[cnt].r = m;//给最后一个加上右边界 
    119     if(tot != n-1) {
    120         puts("0");
    121         return;
    122     }
    123     for(int i = 1;i <= n; ++ i) father[i] = i;
    124     for(int i = 1;i <= n; ++ i) son[i] = 1;
    125     ans = 1;//初始化 
    126     for(int i = 1;i <= cnt; ++ i){
    127         sum = 0;
    128         dfs(i,a[i].l,0);
    129         ans = ans * sum % mod;
    130         for(int j = a[i].l;j <= a[i].r; ++ j){
    131             int fx = find2(edge[j].u);
    132             int fy = find2(edge[j].v);//按最小生成树连边 
    133             if(fx != fy) merge1(fx,fy);
    134         }
    135     }
    136     printf("%d",ans);
    137 }
    138 
    139 int main(){
    140 //    file();
    141     readdata();
    142     work();
    143     return 0;
    144 }
    View Code

    为你,所向披靡!

  • 相关阅读:
    ch_5102 Mobile Service
    ch_POJ1201 Intervals
    [CodeVs]谁是赢家
    树上莫队
    [NOI2009]管道区珠
    拉格朗日差值
    Simpson&自适应Simpson
    数论学习笔记
    hibernate FetchType理解
    Hibernate的generator属性之意义
  • 原文地址:https://www.cnblogs.com/Mandy-H-Y/p/11514241.html
Copyright © 2020-2023  润新知