生成树相关模板(续)

发现一个写太多编辑器比较卡。继续,这也就是一些小点了。
完全图生成树计数
Cayley公式: f ( n ) = n n − 2 f(n) = n^{n-2} f(n)=nn2

有向图树形图计数
树形图计数仍适用Matrix-Tree定理,其Kirchhoff矩阵: 外向树:所计算的矩阵中的对角线元素代表的是点的入度,而且除对角线元素外,代表的有向边是否存在(存在就设为-1);内向图树:对角线元素代表的是点的出度。代表的有向边是否存在(存在就设为-1)。求n-1阶主子式时,删去第i行第i列,就是求以i为根的树形图的数量。
此时答案还需要除(n-1)。具体情况,请看Matrix-Tree定理总结部分。

最小曼哈顿距离生成树
给你一堆二维平面的n个点,两两点之间按照曼哈顿距离连边,问最小生成树。如果两两连边,边数量会炸。

/*
曼哈顿距离生成树
用两个坐标轴和y=x, y=-x把二维区间分成8个部分. 紧挨y轴正方向右侧的一个区域记为R1
有结论: 如果枚举每个点作为原点 这八个部分每个部分只可能分别连接一个最近点. 
而在R1种对于 代原点(x0, y0), 有以下关系
xk >= x0 && yk-xk >= y0 - x0
我们根据x排序离线树状数组询问yi-xi. 问(xi+yi)最小值及点的编号. 
连边.

*/


// POJ 3241
// 找生成树第k大的边. 
const int N =2e5+5;
const int mod = 31011;
const int inf = 0x3f3f3f3f;
int c[N], id[N]; // c[] 树状数组最小值, id[] 树状数组最小值位置. 
// 树状数组也可以维护最值, 但是只能ask前缀, 没法看区间. 
void init()
{ // 初始化数组数组.
	memset(c, inf, sizeof(c));
	memset(id, -1, sizeof(id));
}
void add(int x, int val, int _id)
{ // 在x点插入值, 这个点(xi, yi) : xi+yi = val, i= _id;
	for (; x; x -= x & -x) if (c[x] > val)
	{
		c[x] = val; id[x] = _id;
	}
}
int ask(int x, int m)
{ // 询问大于x = (yi - xi) 的 (xi+yi) 最小值
	int minval = inf;
	int ans = -1;
	for (; x <= m; x += x & -x) if (minval > c[x])
	{
		minval = c[x]; ans = id[x];
	}
	return ans;
}
struct Node
{ //存点
	int x, y; 
	int id;
	friend bool operator < (const Node & a, const Node & b)
	{
		if (a.x !=b.x) return a.x < b.x;
		return a.y < b.y;
	}
} pos[N];
struct Ed
{ //存边
	int x, y, w;
	Ed() {}
	Ed(int x, int y, int w)
	: x(x), y(y), w(w) {}
	friend bool operator < (const Ed & a, const Ed & b)
	{
		return a.w < b.w;
	}
}ed[N], resed[N];
int tot, restot, cntt;
int fa[N], a[N], b[N];
inline int getn(int x)
{ //离散找值
	return lower_bound(b+1, b+cntt, x) - b;
}
inline int getdis(int i, int j)
{
	return abs(pos[i].x - pos[j].x) + abs(pos[i].y - pos[j].y);
}
void build(int n)
{ //建树
    // 按照x坐标排序
	sort(pos+1, pos+n+1);
	for (int i = 1; i <= n; i++)
		a[i] = b[i] = pos[i].y - pos[i].x; // 准备离散
	sort(b+1, b+n+1);
	cntt = unique(b+1, b+n+1) - b;
	init();
	for (int i = n; i >= 1; i--)
	{ // 离线操作, 边查边加
		int poss = getn(a[i]);
		int ans = ask(poss, cntt-1);
		if (ans != -1) // 有边建边
			ed[++tot] = Ed(pos[i].id, pos[ans].id, getdis(i, ans));
        
		add(poss, pos[i].x+pos[i].y, i);
        // 查完了, 把这个点压进数组
	}
}
void tran(int n)
{ // 坐标转化, 将R1~R4 依次转到R1位置. 每个建一遍边. 
	for (int dir = 1; dir <= 4; dir++)
	{
		if (dir == 2 || dir == 4)
		{
			for (int i = 1; i <= n; i++)
				swap(pos[i].x, pos[i].y);
		}
		else if (dir == 3)
		{
			for (int i = 1; i <= n; i++)
				pos[i].x = -pos[i].x;
		}
		build(n);
	}
}
int fi(int x)
{
	if (x == fa[x]) return x;
	return fa[x] = fi(fa[x]);
}
void kruskal(int n)
{  // Kruskal过程
	restot = 0;
	for (int i = 1; i <= n; i++) fa[i] = i;
	sort(ed+1, ed+tot+1);
	for (int i = 1; i <= tot; i++)
	{
		int x = ed[i].x, y = ed[i].y;
		int _x = fi(x), _y = fi(y);
		if (_x != _y)
		{
			fa[_x] = _y;
			resed[++restot] = Ed(x, y, ed[i].w);
		}
	}
}
int main()
{
	int n, k;
	scanf("%d%d", &n, &k);
	tot = 0; restot = 0;
	for (int i = 1; i <= n; i++)
	{
		scanf("%d%d", &pos[i].x, &pos[i].y);
		pos[i].id = i;
	}
	tran(n);
	kruskal(n);
	sort(resed+1,resed+restot+1);
	printf("%d\n", resed[restot - k + 1].w);
	return 0;
}

Matrix-Tree定理总结
Matrix-Tree定理的最普遍情况:
现存在带权有向图:
我们设其带权入度矩阵为 A i n A_{in} Ain(对角线为该点的入度边的权值和,其余为0)

再设其带权出度矩阵为 A o u t A_{out} Aout(对角线为该点的出度边的权值和,其余为0)

最后设其邻阶矩阵 D D D(如果存在边 < x , y > <x,y> <x,y>,权值为 z z z,那么, D x , y D_{x,y} Dx,y+= z z z

那么,对于以 i i i为根的所有最小外向树形图的边权总和为: ( A i n − D ) (A_{in}-D) (AinD)的去掉第i行第i列的n-1次主子树的行列式的值。最小内向树形图的边权总和为: ( A o u t − D ) (A_{out}-D) (AoutD)的去掉第i行第i列的n-1次主子树的行列式的值。

显然,无向图生成树计数和有向图的树形图计数都是其一个特殊情况。
P6178 【模板】Matrix-Tree 定理
这里揭示一个更加普遍的情况:

// % everyone
#include <cstdio>
#include<iostream>
#include<cstring>
#include <map>
#include <queue>
#include <set>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#include <vector>
#include <string>
#include <list>
#include <bitset>
#include <array>
#include <cctype>
#include <unordered_map>
#include <time.h>

namespace OI {
    double start_time = 0.0;
    void read_f(int flag = 0) { freopen("0.in", "r", stdin); if(!flag) freopen("0.out", "w", stdout); }
    void fast_cin() { std::ios::sync_with_stdio(false); std::cin.tie(); }
    void run_time() { std::cout << "\nESC in : " << ( clock() - start_time ) * 1000.0 / CLOCKS_PER_SEC << "ms" << std::endl; }
    void ct() { start_time = clock(); return; }
}
using namespace OI;
template <typename T>
bool bacmp(const T & a, const T & b) { return a > b; }
template <typename T>
bool pecmp(const T & a, const T & b) { return a < b; }

#define ll long long
#define ull unsigned ll
#define _min(x, y) ((x)>(y)?(y):(x))
#define _max(x, y) ((x)>(y)?(x):(y))
#define max3(x, y, z) ( max( (x), max( (y), (z) ) ) )
#define min3(x, y, z) ( min( (x), min( (y), (z) ) ) )
#define pr make_pair
#define pb push_back
using namespace std;

const int N = 512;
const int mod = 1e9+7;
ll _pow(ll a, ll b)
{
    ll res = 1;
    while(b)
    {
        if (b & 1) res = res * a % mod;
        b >>= 1;
        a = a * a % mod;
    }
    return res % mod;
}
int K[N][N];
int n, m;
ll gauss(int n)
{ // 高消主过程
    ll ans = 1;
    for (int i = 2; i <= n; i++)
    {
        for (int j = i + 1; j <= n; j++)
        {
            if (!K[i][i] && K[j][i])
            {
                ans = -ans; swap(K[i], K[j]); break;
            }
        }
        int inv = _pow(K[i][i], mod-2);
        for (int j = i + 1; j <= n; j++)
        {
            int tmp = 1ll * K[j][i] * inv % mod;
            for (int k = i; k <= n; k++)
                K[j][k] = (K[j][k] - 1ll * K[i][k] * tmp % mod + mod) % mod;
        }
    }
    for (int i = 2; i <= n; i++) ans = 1ll * ans * K[i][i] % mod;
    return ans%mod;
}
int main()
{
    int op;
    scanf("%d%d%d", &n, &m, &op);
    while(m--)
    {
        int x, y, z; 
        scanf("%d%d%d", &x, &y, &z);
        if (op == 0)
        {
            K[x][x] = (K[x][x] + z) % mod; K[y][y] = (K[y][y] + z) % mod;
            K[x][y] = (K[x][y] - z + mod) % mod; K[y][x] = (K[y][x] - z + mod) % mod;
            
        }
        else
        {
            K[y][y] = (K[y][y] + z) % mod;
            K[x][y] = (K[x][y] - z + mod) % mod;
        }
        
    }
    printf("%lld\n", gauss(n));
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值