zoukankan      html  css  js  c++  java
  • BZOJ1016 [JSOI2008]最小生成树计数

    Description

    给定一个n个点m条边的无向图,求它的最小生成树个数。(nleq100,mleq1000),相同权值的边不超过10条。

    Solution

    考虑 Kruskal 算法求最小生成树的流程。

    我们把权值相同的边分为一组,显然 Kruskal 是连续考虑到这组边。并且无论这组边之间的顺序如何,处理完它们之后并查集的状态应该是相同的(否则很容易退出矛盾)。

    那么做法就显然了:

    考虑所有最小权值的边,连接这些边之后对每个连通块求生成树计数;之后将每个连通块缩成一个点,再考虑第二小权值的边;以此类推,直到只剩下一个连通块为止。

    生成树可以使用Matrix-Tree Theorem。由于保证相同权值的边不超过10条,还可以搜索。代码中使用了前者。

    Code

    #include <algorithm>
    #include <cstdio>
    const int mod = 31011;
    const int mod1 = 3;
    const int mod2 = 10337;
    const int g1 = 20674;
    const int g2 = 10338;
    const int N = 105;
    const int M = 1005;
    int pre[N], nxt[M * 2], to[M * 2], c[M * 2], cnt = 1;
    int n, m, fa[N];
    int Find(int x) { return fa[x] ? fa[x] = Find(fa[x]) : x; }
    int pow_mod(int a, int b, int p) {
      int ans = 1;
      for (; b; b >>= 1, a = a * a % p)
        if (b & 1) ans = ans * a % p;
      return ans;
    }
    inline int inv(int a, int p) { return pow_mod(a, p - 2, p); }
    struct Edge{
      int a, b, v;
      bool operator<(const Edge &e)const{
        return v < e.v;
      }
      void addEdge() {
        a = Find(a); b = Find(b);
        nxt[cnt] = pre[a];
        c[cnt] = v;
        to[pre[a] = cnt++] = b;
        nxt[cnt] = pre[b];
        c[cnt] = v;
        to[pre[b] = cnt++] = a;
      }
    }e[M];
    int A[N][N], num, B[N][N], pos[N];
    void dfs(int x, int cc, int f) {
      if (x != f) fa[x] = f;
      pos[x] = num++;
      for (int i = 0; i <= pos[x]; ++i)
        A[i][pos[x]] = A[pos[x]][i] = 0;
      for (int i = pre[x]; c[i] == cc; i = nxt[i]) {
        int u = to[i];
        if (!~pos[u]) dfs(u, cc, f);
        --A[pos[x]][pos[u]];
        ++A[pos[x]][pos[x]];
      }
    }
    int solve(int A[N][N], int n, int p) {
      --n;
      int f = 1;
      for (int i = 0; i < n; ++i) {
        int j = i;
        while (!(A[i][j] % p) && j < n) ++j;
        if (j >= n) return 0;
        if (j != i) {
          f *= -1;
          for (int k = i; k < n; ++k)
            std::swap(A[i][k], A[j][k]);
        }
        int v = inv(A[i][i], p);
        f = f * A[i][i] % p;
        for (int k = i; k < n; ++k)
          A[i][k] = A[i][k] * v % p;
        for (int j = i + 1; j < n; ++j)
          for (int k = n - 1; k >= i; --k)
            A[j][k] = (A[j][k] - A[j][i] * A[i][k]) % p;
      }
      return f;
    }
    int ans1 = 1, ans2 = 1;
    void solve() {
      for (int i = 0; i < num; ++i)
        for (int j = 0; j < num; ++j)
          B[i][j] = A[i][j];
      ans1 = ans1 * solve(A, num, mod1) % mod1;
      ans2 = ans2 * solve(B, num, mod2) % mod2;
    }
    int main() {
      scanf("%d%d", &n, &m);
      for (int i = 0; i < m; ++i)
        scanf("%d%d%d", &e[i].a, &e[i].b, &e[i].v);
      std::sort(e, e + m);
      for (int i = 0, j; i < m; ) {
        j = i;
        while (j < m && e[j].v == e[i].v) ++j;
        for (int k = i; k < j; ++k) e[k].addEdge();
        for (int k = 1; k <= n; ++k) pos[k] = -1;
        for (int k = 1; k <= n; ++k) if (Find(k) == k) {
          num = 0;
          dfs(k, e[i].v, k);
          solve();
        }
        i = j;
      }
      int t = 0;
      for (int i = 1; i <= n; ++i) t += (Find(i) == i);
      printf("%d
    ", t > 1 ? 0 : ((ans1 * g1 + ans2 * g2) % mod + mod) % mod);
      return 0;
    }
    
  • 相关阅读:
    linux下删除乱码文件
    linux修改网卡延时、带宽、丢包等
    连接远程系统里的vbox虚拟机
    Linux路由功能
    关于C语言的取整函数
    新博客
    NEU1217之神风堂最少人数 自己在精度上吃了苦头
    sprintf sscanf自己应该会的小点
    【转】妙解亲和数
    redeclared as different kind of symbol
  • 原文地址:https://www.cnblogs.com/y-clever/p/8512274.html
Copyright © 2011-2022 走看看