【校内训练2019-06-24】Sort

【思路要点】

  • 考虑 k = 0 k=0 k=0 的情况,当且仅当 A A A 为单位置换,步数为 0 0 0 ,否则,当且仅当 A A A 中的置换环长度均不超过 2 2 2 ,步数为 1 1 1 ,这两种情况的方案数均为 1 1 1 ,以下讨论排除了这两种情况。
  • 考虑一个环的情况,不难构造出一种步数为 2 2 2 的解,因此步数应当为 2 2 2 ,不失一般性地,我们认为需要在两次操作内将 1 , 2 , … , N 1,2,\dots,N 1,2,,N 变为 2 , 3 , … , N , 1 2,3,\dots,N,1 2,3,,N,1
    将排列如下对齐,并写下中间步骤的第一个位置。
    1 , 2 , 3 , … , N x , 0 , 0 , … , 0 2 , 3 , 4 , … , 1 1,2,3,\dots,N\\x,0,0,\dots,0\\2,3,4,\dots,1 1,2,3,,Nx,0,0,,02,3,4,,1
    可以发现,若 x ≠ 1 x\ne1 x̸=1 ,则可以填出中间步骤的第 x x x 个位置为 1 1 1 ,若 x ≠ 2 x\ne2 x̸=2 ,则同样可以填出中间步骤的某个位置为 2 2 2 ,在此过程中,当填入 a   ( a &lt; N ) a\ (a&lt;N) a (a<N) 个数时,中间步骤至少 a + 1 a+1 a+1 个位置是确定的,且不会出现一个元素需要同时出现在两处的情况,因此一个环的情况的方案由 x x x 唯一确定,为 N N N
  • 对于两个长度 N N N 相等的环,以 N = 3 N=3 N=3 为例,考虑将其如下对齐:
    1 , 2 , 3 , 4 , 5 , 6 x , 0 , 0 , 0 , 0 , 0 2 , 3 , 1 , 5 , 6 , 4 1,2,3,4,5,6\\x,0,0,0,0,0\\2,3,1,5,6,4 1,2,3,4,5,6x,0,0,0,0,02,3,1,5,6,4
    类似地,我们可以证明当填入的 x x x 在第二个环上,方案唯一确定。因此其方案数为 N 2 + N N^2+N N2+N 。同时,这也表明了不存在三个环之间的互动。
  • 那么,对于 i i i 个长度为 N N N 的环,记方案数为 d p i dp_i dpi ,我们便可以用动态规划来解决
    d p i = N × d p i − 1 + N × ( i − 1 ) × d p i − 2 dp_i=N\times dp_{i-1}+N\times(i-1)\times dp_{i-2} dpi=N×dpi1+N×(i1)×dpi2
  • 对于长度不相等的环,用与上文类似的做法可以证明不存在 2 2 2 步以内的交换方式,因此各个长度的环相互独立,可以分别 d p dp dp
  • 对于 k ≠ 0 k\ne0 k̸=0 的情况,图中给出的就是若干个环和链,枚举链穿成环的方式即可。
  • 时间复杂度 O ( N L o g V + k × b e l l ( k ) ) O(NLogV+k\times bell(k)) O(NLogV+k×bell(k)) ,以下代码为实现方便,其复杂度为 O ( N L o g 2 V + k × b e l l ( k ) ) O(NLog^2V+k\times bell(k)) O(NLog2V+k×bell(k))

【代码】

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 5;
const int P = 1e9 + 7;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
template <typename T> void write(T x) {
	if (x < 0) x = -x, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
	write(x);
	puts("");
}
int power(int x, int y) {
	if (y == 0) return 1;
	int tmp = power(x, y / 2);
	if (y % 2 == 0) return 1ll * tmp * tmp % P;
	else return 1ll * tmp * tmp % P * x % P;
}
void update(int &x, int y) {
	x += y;
	if (x >= P) x -= P;
}
bool vis[MAXN], ind[MAXN];
int n, m, k, cur, ansf, ansg, sum[MAXN][2];
int a[MAXN], len[MAXN], cnt[MAXN], fac[MAXN];
vector <int> dp[MAXN], inv[MAXN];
void work(int pos, int tot) {
	if (pos > k) {
		int ways = 1, now = cur;
		for (int i = 1; i <= tot; i++) {
			ways = ways * fac[sum[i][0] - 1];
			now = 1ll * now * inv[sum[i][1]][cnt[sum[i][1]]] % P;
			cnt[sum[i][1]]++;
			now = 1ll * now * dp[sum[i][1]][cnt[sum[i][1]]] % P;
		}
		if (cnt[1] == n) update(ansg, ways);
		else if (cnt[1] + 2 * cnt[2] == n) {
			ansf += ways;
			update(ansg, ways);
		} else {
			ansf += 2 * ways;
			update(ansg, 1ll * ways * now % P);
		}
		for (int i = 1; i <= tot; i++)
			cnt[sum[i][1]]--;
		return;
	}
	for (int i = 1; i <= tot; i++) {
		sum[i][0] += 1;
		sum[i][1] += len[pos];
		work(pos + 1, tot);
		sum[i][0] -= 1;
		sum[i][1] -= len[pos];
	}
	sum[tot + 1][0] += 1;
	sum[tot + 1][1] += len[pos];
	work(pos + 1, tot + 1);
	sum[tot + 1][0] -= 1;
	sum[tot + 1][1] -= len[pos];
}
int main() {
	freopen("sort.in", "r", stdin);
	freopen("sort.out", "w", stdout);
	read(n), read(k), fac[0] = 1;
	for (int i = 1; i <= k; i++)
		fac[i] = 1ll * fac[i - 1] * i % P;
	for (int i = 1; i <= n; i++) {
		read(a[i]);
		ind[a[i]] = true;
	}
	for (int i = 1; i <= n; i++) {
		dp[i].resize(n / i + 1);
		inv[i].resize(n / i + 1);
		dp[i][0] = inv[i][0] = 1;
		for (int j = 1; j <= n / i; j++) {
			dp[i][j] = 1ll * i * dp[i][j - 1] % P;
			if (j >= 2) update(dp[i][j], 1ll * i * (j - 1) % P * dp[i][j - 2] % P);
			inv[i][j] = power(dp[i][j], P - 2);
			assert(dp[i][j] != 0);
		}
	}
	for (int i = 1; i <= n; i++)
		if (!ind[i]) {
			int pos = i, pts = 0;
			while (pos != 0) {
				vis[pos] = true;
				pts++, pos = a[pos];
			}
			len[++m] = pts;
		}
	for (int i = 1; i <= n; i++)
		if (!vis[i]) {
			int pos = i, pts = 0;
			while (!vis[pos]) {
				vis[pos] = true;
				pts++, pos = a[pos];
			}
			cnt[pts]++;
		}
	cur = 1;
	for (int i = 1; i <= n; i++)
		cur = 1ll * cur * dp[i][cnt[i]] % P;
	work(1, 0);
	writeln(ansf);
	writeln(ansg);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值