平面图完美匹配计数——(这个叫)FKT算法(Fast Kasteleyns technique?)【求1*2多米诺骨牌铺格子的(博主)已知最快算法】

唐老师的博客[n<=100可做]
歪果仁的超强PPT,看完之后你就知道怎么做[nm<=500的情况](只要你忽略证明)
搜FKT,完美匹配计数统统搜不到。
只有搜51nod1034 v3。。。
或是 Counting perfect matchings in planar graphs
才搜的到。
这个在国内也太冷了吧。
我来用中文复述一遍PPT内容:
1.1*2骨牌铺格子可以转化为求完美匹配数 P e r f e r t m a t c h Perfertmatch Perfertmatch
2.定义排列M,让M中第 2 i 2i 2i个和第 2 i + 1 2i+1 2i+1个匹配。
发现 P e r f e c t m a t c h = ∑ M ∏ A 2 i , 2 i + 1 Perfectmatch = \sum_{M}\prod A_{2i,2i+1} Perfectmatch=MA2i,2i+1
A i , j = 1 A_{i,j}=1 Ai,j=1当且仅当 i , j i,j i,j间有边。
3.这个形式让我们想到了行列式,结合矩阵树定理的经验开始往行列式靠。
然后发现按 M M M的逆序对数为奇数乘一个 − 1 -1 1否则不乘后得到的:
P f a f f i a n = ∑ M s g n ( M ) ∏ A 2 i , 2 i + 1 Pfaffian = \sum_{M}sgn(M)\prod A_{2i,2i+1} Pfaffian=Msgn(M)A2i,2i+1
s g n ( M ) sgn(M) sgn(M)即为-1或1。
如果 A A A满足 ∀ i , j   A i , j = − A j , i \forall i,j\ A_{i,j} = -A_{j,i} i,j Ai,j=Aj,i
有:
P f a f f i a n 2 = d e t ( A ) Pfaffian^2 = det(A) Pfaffian2=det(A)
然后如果还有 ∣ P f a f f i a n ∣ = P e r f e c t m a t c h |Pfaffian| = Perfectmatch Pfaffian=Perfectmatch
4.明显如果我们把边变成有向边就可以满足 ∀ i , j   A i , j = − A j , i \forall i,j\ A_{i,j} = -A_{j,i} i,j Ai,j=Aj,i
考虑何时 ∣ P f a f f i a n ∣ = P e r f e c t m a t c h |Pfaffian| = Perfectmatch Pfaffian=Perfectmatch
用一下平面图的性质。
找到内部不包含点的环,保证环上有奇数条顺时针的边。
那么两个相交的环,相交部分对于一个环是顺时针,对于另一个就是逆时针。
合并成的大环也是有奇数条顺时针的边。
然后就莫名其妙证明每个环有奇数条顺时针的边安排可以保证 ∣ P f a f f i a n ∣ = P e r f e c t m a t c h |Pfaffian| = Perfectmatch Pfaffian=Perfectmatch
那么只要找到安排的方法即可。
5.在原图找一颗生成树,所有边朝叶子方向,然后对于对偶图,找没有在生成树上出现的边,边两边的点连边。(好像不用打点定位的对偶图也就七八十来行吧)
可以证明这是一棵树,然后从叶子开始往上操作,给每个点到父亲的边定向使得这个点所代表的平面区域环中有奇数条顺时针的边。
然后对于这个邻接矩阵求det再开方就行了。

6.骨牌覆盖的图可以把 A A A变成 n ∗ n n*n nn的,看唐老师博客吧,博主不行了。。。。。。

考虑一个 n n n m m m 列的网格图,我们需要用 1 ∗ 2 1∗2 12 的骨牌将其覆盖。
网格图的内部有一些凸起的边界,骨牌无法平稳的放置在凸起上,因此有些放置骨牌的方法是不可行的。
考虑如下的网格图,红色的边界表示有凸起的边界。蓝色的线表示骨牌,这也是这个图唯一的合法方案。
现在某人想要从所有合法方案中选出两个合法方案组成一对合法方案(可以相同) A , B A,B A,B B , A B,A B,A算两个不同的方案对。他想要知道有多少方案对?
输入格式
第一行两个整数 n , m n,m n,m 表示网格图的大小。
接下来 n n n 行,每行 m − 1 m−1 m1 0 / 1 0/1 0/1,第 i i i 行第 j j j 个变量表示第 i i i 行的第 j j j 个格子和 j + 1 j+1 j+1个格子中间是否有凸起的边界, 1 1 1 表示有。
接下来 n − 1 n−1 n1 行,每行 m m m 0 / 1 0/1 0/1,第 i i i 行第 j j j 个变量表示第 i i i 行和第 i + 1 i+1 i+1 行的第 j j j 个格子中间是否有凸起的边界,表示方法同前。
输出格式
输出一行一个整数,表示答案,答案对 998244353 998244353 998244353 取模。
给个代码(不是我打的):


#include 
#define EB emplace_back

typedef long long ll;
typedef std::vector <int> vector;
const int N = 5000, M = 66666, mod = 998244353;

struct edge {
	int u, v;
	edge (int u0 = 0, int v0 = 0) : u(u0), v(v0) {}
} e[M];

int R, C, V, E = 0;
int p[N], mat[N][N], elim[N];
int to[M], first[N], next[M];
int orient[M];

inline void addedge(int u, int v) {
//	fprintf(stderr, "addedge %d %d\n", u, v);
	e[++E] = edge(u, v), next[E] = first[u], first[u] = E;
	e[++E] = edge(v, u), next[E] = first[v], first[v] = E;
}

inline int join(int r, int c) {return (r - 1) * C + 1;}
inline void split(int id, int &r, int &c) {r = --id / C + 1, c = id % C + 1;}
inline int ancestor(int x) {return p[x] == x ? x : (p[x] = ancestor(p[x]));}
inline bool Union(int x, int y) {
	if ((x = ancestor(x)) == (y = ancestor(y))) return true;
	return p[x] = y, false;
}

ll PowerMod(ll a, int n, ll c = 1) {for (; n; n >>= 1, a = a * a % mod) if (n & 1) c = c * a % mod; return c;}

int gauss(int n) {
	int i, j, k, *t, det = 1, cnt; ll coe;
	static int *m[N];
	for (i = 0; i < n; ++i) m[i] = mat[i];
	for (i = 0; i < n; ++i) {
		for (j = i; j < n && !m[j][i]; ++j);
		if (j == n) return 0;
		if (i != j) det = mod - det, std::swap(m[i], m[j]);
		det = (ll)det * m[i][i] % mod;
		coe = PowerMod(m[i][i], mod - 2);
		for (j = 0; j < n; ++j) m[i][j] = m[i][j] * coe % mod;
		for (cnt = 0, k = i; k < n; ++k) if (m[i][k]) elim[cnt++] = k;
		for (k = i + 1; k < n; ++k) if (m[k][i]) {
			coe = mod - m[k][i];
			for (t = elim; t != elim + cnt; ++t)
				m[k][*t] = (m[k][*t] + coe * m[i][*t]) % mod;
		}
	}
	return det;
}

namespace Planar {
	#define ad(x) (((x - 1) ^ 1) + 1)
	int F = 0;
	int belF[M], remain[M], que[M];
	vector face[M];

	inline bool judge(int A, int B, int C, int D) {
		if (B == A + ::C) return (D - 1) % ::C < (C - 1) % ::C;
		if (B == A - ::C) return (C - 1) % ::C < (D - 1) % ::C;
		if (B == A + 1) return C < D;
		if (B == A - 1) return D < C;
		abort();
	}

	void dfs(int i) {
		int j, x = e[i].u, y = e[i].v, z, bestV = 0, bestE = 0;
//		fprintf(stderr, "search face #%d edge #%d (%d, %d)\n", F, i, x, y);
		belF[i] = F, face[F].EB(i);
		for (j = first[y]; j; j = next[j]) {
			if ((z = e[j].v) == x) continue;
			if (!bestV || judge(x, y, z, bestV)) bestV = z, bestE = j;
		}
		if (!bestE) {
			for (j = first[y]; j; j = next[j]) if (e[j].v == x) break;
			bestV = x, bestE = j;
		}
		if (belF[bestE] != F) dfs(bestE);
	}

	int main() {
		int i, f, h, t = 0; bool o;
		for (i = 1; i <= E; ++i)
			if (!belF[i]) ++F, dfs(i), remain[F] = face[F].size();
		for (i = 1; i <= F; ++i)
			for (int e : face[i])
				if (orient[e] && --remain[i] == 1) que[t++] = i;
		for (h = 0; h < t; ++h) {
			f = que[h], o = true;
			if (!remain[f]) continue;
			for (int e : face[f])
				if (orient[e]) o ^= orient[e] != e;
				else i = e;
			orient[ad(i)] = orient[i] = (o ? ad(i) : i);
			if (--remain[ f = belF[ad(i)] ] == 1) que[t++] = f;
		}
		for (i = 1; i <= E; ++i) assert(orient[i]);
		return 0;
	}
}

int main() {
	int i, j, v, n;
	scanf("%d%d", &R, &C), V = R * C;
	if (R & C & 1) return putchar(48), putchar(10), 0;
	for (n = i = 1; i <= R; ++i, ++n)
		for (j = 1; j < C; ++j, ++n)
			if (scanf("%d", &v), !v) addedge(n, n + 1);
	for (n = i = 1; i < R; ++i)
		for (j = 1; j <= C; ++j, ++n)
			if (scanf("%d", &v), !v) addedge(n, n + C);
	std::iota(p + 1, p + (V + 1), 1);
	for (i = 1; i <= E; i += 2)
		if (!Union(e[i].u, e[i].v)) {
			orient[i] = orient[i + 1] = i;
//			fprintf(stderr, "connect %d %d\n", e[i].u, e[i].v);
		}
	Planar::main();
	for (i = 1; i <= E; ++i) if (orient[i] == i)
		mat[e[i].u - 1][e[i].v - 1] = 1, mat[e[i].v - 1][e[i].u - 1] = mod - 1;
	printf("%d\n", gauss(V));
	return 0;
}

你可能感兴趣的:(模板,DP,数论,数据结构,矩阵优化,性质分析)