洛谷 「CF1534C Little Alawn's Puzzle「」

传送门

\(\texttt{Description}\)

一个 \(2\times n\) 的矩阵,可以操作任意次,每次操作是交换某一列上的 \(2\) 个数字,经过操作之后,有多少种同一行没有相同数字的情况。方案数对 \(10^9+7\) 取模。

\(\texttt{Solution}\)

这是一道好题(当然是对于我这种屑来说)。

  • 我们知道,我们交换的一定是某些整个的循环置换。

  • 循环置换的意思就是在这个置换里面的每个数都出现了 \(2\) 次,可以理解为是一个封闭的块。

  • 假设有 \(cnt\) 个循环置换,那么答案其实就是 \(\sum\limits_{i=0}^{cnt}C_{cnt}^i\),这个应该是很好理解(代码实现中写的是这种)。但是还有另外一种简单的求法,就是 \(2^{cnt}\),为什么呢?因为每个都有选或不选两种情况。

  • 至于如何求出 \(cnt\),有两种方法:

    • 直接 dfs,这里我选择了 dfs。

    • 并查集,将给出的矩阵每列的两个数合并,再统计连通块数量即可。

时间复杂度:\(\mathcal{O}(n\log n)\)

\(\texttt{Code}\)

#include <cstdio>
#include <iostream>
#include <cstring>
#include <map>

using namespace std;

typedef long long ll;
const ll mod = 1e9 + 7;
inline int Min (int x, int y) {return x < y ? x : y;}
inline int Max (int x, int y) {return x > y ? x : y;}

int a[400005], b[400005];
bool vis[400005];
map<int, int> mp;

inline ll quickPow (ll a, ll k, ll p) {
	ll res = 1;
	a %= p;
	while (k) {
		if (k & 1)
			res = res * a % p;
		k >>= 1;
		a = a * a % p;
	}
	return res;
}

inline ll inv (ll x, ll p) {
	return quickPow(x, p - 2, p);
}

inline void dfs (int i) {
	if (vis[b[i]])
		return;
	vis[b[i]] = true;
	dfs(mp[b[i]]);
}

int main () {
	int T;
	scanf("%d", &T);
	while (T--) {
		int n, cnt = 0;
		scanf("%d", &n);
		for (int i = 1; i <= n; i++)
			scanf("%d", &a[i]), mp[a[i]] = i;
		for (int i = 1; i <= n; i++)
			scanf("%d", &b[i]);
		memset(vis, false, sizeof(vis));
		for (int i = 1; i <= n; i++)
			if (!vis[a[i]]) {
				vis[a[i]] = true;
				dfs(i);
				cnt++;
			}
		ll ans = 1, tot = 1ll * cnt, fac = 1;
		for (int i = 1; i <= cnt; i++) {
			fac = fac * i % mod;
			ans = (ans + tot * inv(fac, mod) % mod) % mod;
			tot = tot * (cnt - i) % mod;
		}
		printf("%lld\n", ans);
	}
	return 0;
}
posted @ 2021-06-20 12:45  Sham_Devour  阅读(105)  评论(0)    收藏  举报