关于生成树计数问题和多种情况

      大的道理和理论我就不说了,有什么理论上的疑惑我推荐下这个地址:

      https://blog.csdn.net/u013010295/article/details/47451451

     一:无向图中的生成树计数;

      这种情况下我们要看看是否存在重边的情况,如果没有的话就可以用Matrix-Tree定理和高斯消元直接来解;

      map代表我们的图中的边是否存在,

      而dre便是我们计算所需要的数组,他的对角线元素的值分别代表着某个点她的度的大小(包括入度和出度,其实就是加起来),非对角线元素就是代表着某个边是否存在,存在的话标为-1,不存在的话标为0;

               首先是需要取余的;

#include<cstdio>
#include<iostream>
using namespace std;
#define ll long long int
char map[15][15];
ll dre[105][105];
int  n, m;
ll mod = 1e9;
long long solve(int n)
{
	ll ans = 1;
	for (int s = 1; s < n; s++)
	{
		for (int w = s + 1; w < n; w++)
			while (dre[w][s])
			{
				ll t = dre[s][s] / dre[w][s];
				for (int k = s; k < n; k++)
				{
					dre[s][k] = (dre[s][k] - dre[w][k] * t%mod + mod) % mod;
					swap(dre[s][k], dre[w][k]);
				}
				ans = -ans;
			}
		if (dre[s][s] == 0)
			return 0;
		ans = (ans*dre[s][s]) % mod;
	}
	return (ans + mod) % mod;
}

 其次是不需要取余的

#include<cstdio>
#include<iostream>
#include<cstring>
#include<cmath>
using namespace std;
#define ll long long int
long long dre[55][55];
bool map[55][55];
int n, m, k;
const long long eps = 1e-10;
long long solve(int n)
{
	ll ret = 1;
	for (int i = 1; i<=n; i++)
	{
		for (int j = i + 1; j<=n; j++)
			while (dre[j][i])
			{
				ll t = dre[i][i] / dre[j][i];
				for (int k = i; k<=n; k++)
					dre[i][k] = (dre[i][k] - dre[j][k] * t);
				for (int k = i; k<=n; k++)
					swap(dre[i][k], dre[j][k]);
				ret = -ret;
			}
		if (dre[i][i] == 0)
			return 0;
		ret = ret*dre[i][i];
	}
	if (ret<0)
		ret = -ret;
	return ret;
}
int main()
{
	while (~scanf("%d%d%d", &n, &m, &k)) {
		memset(map, 0, sizeof(map));
		memset(dre, 0, sizeof(dre));
		while (m--)
		{
			int a, b;
			scanf("%d%d", &a, &b);
			map[a][b] = map[b][a] = 1;
		}
		for (int s = 1; s <= n; s++) {
			for (int w = s + 1; w <= n; w++) {
				if (!map[s][w])
					dre[s][s]++, dre[s][w] = -1, dre[w][w]++, dre[w][s] = -1;
			}
		}
		printf("%lld\n", solve(n - 1));
	}
}

精度需求高的: 

#include<cstdio>
#include<iostream>
#include<cstring>
#include<cmath>
using namespace std;
#define ll long long int
long double dre[55][55];
bool map[55][55];
int n, m, k;
const long double eps = 1e-10;
int sgn(long double x)
{
	if (fabs(x)<eps) return 1; return 0;
}
long double solve(int n)
{
	for (int s = 1; s <= n; s++) {
		int maxn = s;
		for (int w = s + 1; w <= n; w++) {
			if (fabs(dre[w][s]) > fabs(dre[maxn][s])) {
				maxn = s;
			}
		}
		if (sgn(dre[maxn][s]))return 0;
		if (maxn != s) {
			for (int w = s; w <= n; w++) {
				swap(dre[s][w], dre[maxn][w]);
			}
		}
		for (int w = s + 1; w <= n; w++) {
			long double temp = dre[w][s] / dre[s][s];
			for (int e = s; e <= n; e++) {
				dre[w][e] -= temp*dre[s][e];
			}
		}
	}
	long double ans = 1;
	for (int s = 1; s <= n; s++) {
		ans *= dre[s][s];
	}
	ans = fabs(ans);
	return ans;
}
int main()
{
	while (~scanf("%d%d%d", &n, &m, &k)) {
		memset(map, 0, sizeof(map));
		memset(dre, 0, sizeof(dre));
		while (m--) 
		{
			int a, b;
			scanf("%d%d", &a, &b);
			map[a][b] = map[b][a] = 1;
		}
		for (int s = 1; s <= n; s++) {
			for (int w = s + 1; w <= n; w++) {
				if (!map[s][w])
					dre[s][s]++, dre[s][w] = -1, dre[w][w]++, dre[w][s] = -1;
			}
		}
		printf("%.0Lf\n", solve(n - 1));
	}
}

       而当存在重边(重边算多次)的时候,就需要用kruskal算法(此代码来自https://www.cnblogs.com/CtrlCV/p/5668752.html

#include <bits/stdc++.h>
using namespace std;
struct edge
{
    int u, v, w, x;
    inline bool operator< (const edge &rhs) const
    {
        return x < rhs.x;
    }
}e[100005];
struct count
{
    int l, r, use;
}g[100005];
int n, m, fa[50005], siz[50005];
   
int getfa(int x)
{
    return fa[x] == x ? x : getfa(fa[x]);
}
   
void link(int u, int v)
{
    if(siz[u] > siz[v]) fa[v] = u, siz[u] += siz[v];
    else fa[u] = v, siz[v] += siz[u];
}
   
bool Kruskal()
{
    int cnt = 0, u, v;
    for(int i = 1; i <= m; ++i)
    {
        u = getfa(e[i].u), v = getfa(e[i].v);
        if(u != v)
        {
            link(u, v);
            ++g[e[i].w].use;
            if(++cnt == n - 1) return true;
        }
    }
    return false;
}
   
int DFS(int w, int i, int k)
{
    if(k == g[w].use) return 1;
    if(i > g[w].r) return 0;
    int ans = 0, u = getfa(e[i].u), v = getfa(e[i].v);
    if(u != v)
    {
        link(u, v);
        ans = DFS(w, i + 1, k + 1);
        fa[u] = u, fa[v] = v;
    }
    return ans + DFS(w, i + 1, k);
}
   
int main()
{
    int u, v, w, ans;
    cin >> n >> m;
    for(int i = 1; i <= n; ++i)
        fa[i] = i, siz[i] = 1;
    for(int i = 1; i <= m; ++i)
    {
        cin >> u >> v >> w;
        e[i] = (edge){u, v, 0, w};
    }
    sort(e + 1, e + m + 1);
    w = 0;
    for(int i = 1; i <= m; ++i)
        if(e[i].x == e[i - 1].x) e[i].w = w;
        else
        {
            g[w].r = i - 1;
            e[i].w = ++w;
            g[w].l = i;
        }
    g[w].r = m;
    ans = Kruskal();
    for(int i = 1; i <= n; ++i)
        fa[i] = i, siz[i] = 1;
    for(int i = 1; i <= w; ++i)
    {
  //      ans = ans * DFS(i, g[i].l, 0) % 1000003; 看题意是否需要余
        for(int j = g[i].l; j <= g[i].r; ++j)
        {
            u = getfa(e[j].u), v = getfa(e[j].v);
            if(u != v) link(u, v);
        }
    }
    cout << ans << endl;
    return 0;
}

   二:有向图的生成树计数;

    外向树:所有边的方向都是从根指向叶子

    内向树:所有边的方向都是从叶子指向根

    无论是求外向树还是内向树,都可以用矩阵树定理来做,不过不同的是,在算外向树的时候,所计算的矩阵中的对角线元素代表的是点的出度,而且除对角线元素外,代表的是又向的路是否存在(存在就设为-1);内向图也一样,只不过对角线元素代表的是点的出度罢了

猜你喜欢

转载自blog.csdn.net/chenshibo17/article/details/81297493