NFLSOJ685 【NFLSPC #3】排列

nflsoj685 【NFLSPC #3】排列

题目大意

给定一个长度为 \(n\) 的排列,现在你可以进行一些操作,每一次操作形如:选取两个数,交换这两个数在排列中的位置。求将这个排列变成 \([1,2,3,...,n]\) 所需要的最少的操作次数。

你不仅要求出最少的操作次数,还要求出有多少种操作的方案使得能在最少的操作次数内将排列变成 \([1,2,3,...,n]\),两个方案不相同当且仅当存在一个数 \(x\),使得第 \(x\) 次操作中两种方案交换的数不相同。方案数需要对 \(998244353\) 取模。

数据范围:\(1\leq n\leq 10^5\)

本题题解

找出排列里所有置换环。设有 \(c\) 个置换环,大小分别为 \(s_1,s_2,\dots,s_c\)。则最少操作次数是 \(\sum_{i=1}^{c}(s_i - 1) = n - c\),这是因为最优方案里,每次操作相当于把一个环拆成两个独立的环。特别地,当环的大小为 \(2\) 时,只需要一次操作,就能将两个元素都复位,所以每个环所需的操作次数是 \(s_i-1\)

考虑求方案数。

\(\text{sum}_i\) 表示前 \(i\) 个环的大小之和。即:\(\text{sum}_i = \sum_{j = 1}^{i - 1}(s_j - 1)\)。(环之间显然是无序的,我们只是按任意顺序枚举这些环,在枚举的过程中顺便求个前缀和。)

\(f_i\) 表示将一个大小为 \(i\) 的环全部复位的方案数(用 \(i-1\) 次操作拆成 \(i\) 个大小为 \(1\) 的环)。假设已经知道了 \(f\) 数组,那么总方案数就等于:

\[\prod_{i=1}^{c}f_i\times{\text{sum}_{i} - i\choose s_i - 1} \]

因为不同环的操作是相互独立的,所以后面的组合数相当于把两个操作序列随意合并。

考虑求 \(f_i\)。首先 \(f_1 = 1\)。当 \(i > 1\) 时,\(f_i\) 的第一步一定是把 \(i\) 拆成两个环,然后变成两个独立子问题。我们枚举其中一个环的大小,注意拆成 \((a,b)\) 与拆成 \((b,a)\) 本质是一样的,所以要除以二。于是:

\[f_i =\frac{1}{2}\sum_{j = 1}^{i - 1}f_j\cdot f_{i - j}\cdot{i - 2\choose j -1}\cdot i \]

其中 \({i - 2\choose j - 1}\) 还是表示将两个操作序列随意合并,乘以 \(i\) 表示任意选择要拆的位置。

朴素 DP 复杂度是 \(O(n^2)\) 的。

改写一下,把组合数拆开,提取只含 \(i\) 的项,得到:\(\displaystyle f_i =\frac{1}{2}\cdot i\cdot (i - 2)!\sum_{j = 1}^{i - 1}f_j\cdot \frac{1}{(j - 1)!}\cdot f_{i - j}\cdot \frac{1}{(i - j - 1)!}\)

发现后半部分是个卷积的形式,并且式子里又有前面的 \(f\) 值,所以可以用分治 FFT 优化。

在一个分治的过程中,考虑已经求出的 \([l,\text{mid}]\)\(f\) 值对 \([\text{mid} + 1, r]\)\(f\) 值的贡献。

具体来说,为了方便,我们钦定 \(j \in [l,\text{mid}]\)\(j\)\(i - j\) 中较大的那个。这样在上述式子的前面,就不需要乘以 \(\frac{1}{2}\) 了。

  • \(l > 1\) 时,\(\forall i \in[\text{mid} + 1, r],j \in[l,\text{mid}]\),一定有:\(j > i - j\)。对 \([l,\text{mid}]\)\([1,r - l]\) 做一个卷积,天然就符合我们想要的效果。
  • \(l = 1\) 时,对 \([1,\text{mid}]\)\([1,\text{mid}]\) 做一个卷积,此时每一对恰好被多统计了一次。我们把结果除以 \(2\) 即可。

如果不钦定 \(j\in[l,\text{mid}]\) 是较大的那个,在 \(l = 1\) 时可能会出现调用到 \(i-j > \text{mid}\) 的情况,而 \([\text{mid} + 1, r]\)\(f\) 值还没有求好,于是就错了。

综上我们得到了一个时间复杂度 \(O(n\log^2 n)\) 的做法。

参考代码

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;

const int MAXN = 1e5;
const int MOD = 998244353;
inline int pow_mod(int x, int i) {
	int y = 1;
	while (i) {
		if (i & 1)
			y = (ll)y * x % MOD;
		x = (ll)x * x % MOD;
		i >>= 1;
	}
	return y;
}
int fac[MAXN + 5], ifac[MAXN + 5];
void facinit(int lim = MAXN) {
	fac[0] = 1;
	for (int i = 1; i <= lim; ++i) fac[i] = (ll)fac[i - 1] * i % MOD;
	ifac[lim] = pow_mod(fac[lim], MOD - 2);
	for (int i = lim - 1; i >= 0; --i) ifac[i] = (ll)ifac[i + 1] * (i + 1) % MOD;
}
inline int comb(int n, int k) {
	if (n < k) return 0;
	return (ll)fac[n] * ifac[n - k] % MOD * ifac[k] % MOD;
}
int n, a[MAXN + 5];
int fa[MAXN + 5], sz[MAXN + 5];
int get_fa(int u) {
	return (u == fa[u]) ? u : (fa[u] = get_fa(fa[u]));
}
void unite(int u, int v) {
	u = get_fa(u);
	v = get_fa(v);
	if (u != v) {
		if (sz[u] > sz[v])
			swap(u, v);
		fa[u] = v;
		sz[v] += sz[u];
	}
}

namespace NTT {

int rev[MAXN * 4 + 5];
int a[MAXN * 4 + 5], b[MAXN * 4 + 5];
void NTT(int a[], int n, int flag) {
	for (int i = 0; i < n; ++i) {
		if (i < rev[i]) {
			swap(a[i], a[rev[i]]);
		}
	}
	for (int i = 1; i < n; i <<= 1) {
		int T = pow_mod(3, (MOD - 1) / (i << 1));
		if (flag == -1) T = pow_mod(T, MOD - 2);
		for (int j = 0; j < n; j += (i << 1)) {
			for (int k = 0, t = 1; k < i; ++k, t = (ll)t * T % MOD) {
				int Nx = a[j + k], Ny = (ll)a[i + j + k] * t % MOD;
				a[j + k] = (Nx + Ny) % MOD;
				a[i + j + k] = (Nx - Ny + MOD) % MOD;
			}
		}
	}
	if (flag == -1) {
		int inv_n = pow_mod(n, MOD - 2);
		for (int i = 0; i < n; ++i) {
			a[i] = (ll)a[i] * inv_n % MOD;
		}
	}
}
void work(int _a[], int _b[], int _res[], int n, int m, int reslen) {
	int lim = 1, ct = 0;
	while (lim <= n + m) {
		lim <<= 1, ct++;
	}
	for (int i = 1; i < lim; ++i) {
		rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (ct - 1));
	}
	for (int i = 0; i < n; ++i) a[i] = _a[i];
	for (int i = n; i < lim; ++i) a[i] = 0;
	for (int i = 0; i < m; ++i) b[i] = _b[i];
	for (int i = m; i < lim; ++i) b[i] = 0;
	NTT(a, lim, 1);
	NTT(b, lim, 1);
	for (int i = 0; i < lim; ++i) a[i] = (ll)a[i] * b[i] % MOD;
	NTT(a, lim, -1);
	for (int i = 0; i < reslen; ++i) _res[i] = a[i];
}
} // namespace NTT


int dp[MAXN * 4 + 5];
int f[MAXN * 4 + 5], g[MAXN * 4 + 5], h[MAXN * 4 + 5];
int inv_2;
void solve(int l, int r) {
	if (l == r) {
		if (l == 1) dp[1] = 1;
		else {
			dp[l] = (ll)dp[l] * l % MOD * fac[l - 2] % MOD;
		}
		return;
	}
	int mid = (l + r) >> 1;
	solve(l, mid);
	if (l == 1) {
		for (int i = 1; i <= mid; ++i) {
			f[i - 1] = (ll)dp[i] * ifac[i - 1] % MOD;
		}
		for (int i = 1; i <= mid; ++i) {
			g[i - 1] = (ll)dp[i] * ifac[i - 1] % MOD;
		}
		NTT :: work(f, g, h, mid, mid, r - 1);
		for (int i = mid + 1; i <= r; ++i) {
			dp[i] = ((ll)dp[i] + (ll)h[i - 2] * inv_2 % MOD) % MOD;
		}
	} else {
		for (int i = l; i <= mid; ++i) {
			f[i - l] = (ll)dp[i] * ifac[i - 1] % MOD;
		}
		for (int i = 1; i <= r - l; ++i) {
			g[i - 1] = (ll)dp[i] * ifac[i - 1] % MOD;
			assert(i <= mid);
		}
		NTT :: work(f, g, h, mid - l + 1, r - l, r - l);
		for (int i = mid + 1; i <= r; ++i) {
			dp[i] = (dp[i] + h[i - l - 1]) % MOD;
		}
	}
	solve(mid + 1, r);
}
int main() {
	facinit();
	cin >> n;
	for (int i = 1; i <= n; ++i) {
		fa[i] = i;
		sz[i] = 1;
	}
	for (int i = 1; i <= n; ++i) {
		cin >> a[i];
		unite(i, a[i]);
	}
	int ans = 1;
	int cur_size = 0;
	inv_2 = pow_mod(2, MOD - 2);
	
//	dp[1] = 1;
//	for (int i = 2; i <= n; ++i) {
//		for (int j = 1; j < i; ++j) {
//			dp[i] = ((ll)dp[i] + (ll)dp[j] * dp[i - j] % MOD * ifac[j - 1] % MOD * ifac[i - j - 1] % MOD) % MOD;
//		}
//		dp[i] = (ll)dp[i] * i % MOD * fac[i - 2] % MOD * inv_2 % MOD;
//	}
	
	solve(1, n);
	for (int i = 1; i <= n; ++i) {
		if (i == get_fa(i)) {
			ans = (ll)ans * dp[sz[i]] % MOD;
			ans = (ll)ans * comb(sz[i] - 1 + cur_size, sz[i] - 1) % MOD;
			cur_size += sz[i] - 1;
		}
	}
	cout << cur_size << " " << ans << endl;
	return 0;
}
posted @ 2020-11-25 23:01  duyiblue  阅读(434)  评论(0编辑  收藏  举报