AGC005D ~K Perm Counting

Statement:

若一个有 \(n\) 个元素的排列 \(P\) 满足对于任意 \(i(1\le i \le n)\) 都有 \(|P_i - i| \ne k\),则这个排列是合法的。现给定 \(n, k\), 问有多少个合法的排列。

Solution:

神仙题啊。

考虑容斥。钦定有 \(i\) 个位置不满足条件,即满足 \(|P_i - i| = k\)

这里有一步关键的转化:我们将每个下标以及元素的值分别看成一个个点,将满足 \(|P_i - i| = k\) 的点连边,组成一个二分图。

如下图:(机房画不了图就偷了一张)(\(k = 1, n = 5\))
image

像这样的左部点代表的是 \(P\) 中的元素,右部点代表的是位置。此时问题转化为在这张二分图上找出 \(i\) 条没有公共端点的边的方案数。注意到这个图由一条条互不相交的链组成,如果只有一条链,我们可以直接考虑 \(\rm dp\) 算。实际上,即使有多条链,我们依然可以把他们接在一起组成一条链,只是每条原链的链头不能向它的前驱连边。

于是令 \(f_{i,j,0/1}\) 为考虑链上前 \(i\) 个节点,选了 \(j\) 条互不相交的边,并且选/不选 \(i\) 与它的前驱的边。

显然有:

\[f_{i,j,0} = f_{i - 1, j, 0} + f_{i - 1, j, 1} \]

\[f_{i,j,1} = f_{i - 1, j - 1, 0} (head[i] = 0 且 j \ne 0) \]

其中 \(head[i] = 1/0\)\(i\) 是/否为原图中某一条链的链头。

最后容斥回来就可以了。

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define id (x + n * pos)
using namespace std;

const int N = 2000 + 10, mod = 924844033;

int n, k, f[2 * N][N][2], tcnt, jc[N];

bool vis[N * 2], head[N * 2];

void dfs(int x, int pos){
	if(x > n || vis[id]) return; vis[id] = true;
//	cout << x << " " << pos << "\n";
	tcnt++; dfs(x + k, pos ^ 1);
}

signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	cin >> n >> k; jc[0] = 1;
	for(int i = 1; i <= n; i++){
		if(!vis[i]) head[tcnt + 1] = true, dfs(i, 0);
		if(!vis[i + n]) head[tcnt + 1] = true, dfs(i, 1);
		jc[i] = (jc[i - 1] * i) % mod;
	} 
	f[1][0][0] = 1;
	for(int i = 2; i <= tcnt; i++){
		for(int j = 0; j <= n; j++){
			f[i][j][0] = (f[i - 1][j][0] + f[i - 1][j][1]) % mod;
			if((!head[i]) && j) f[i][j][1] = f[i - 1][j - 1][0];
//			cout << i << " " << j << " " << f[i][j][0] << " " << f[i][j][1] << "\n";
		}
	}
	int ans = 0;
	for(int i = 0; i <= n; i++) ans = (ans + ((i & 1) ? -1 : 1) * (((f[2 * n][i][0] + f[2 * n][i][1]) % mod) * jc[n - i]) % mod + mod) % mod;
	cout << ans;
	
	return 0;
} 
posted @ 2024-05-09 13:22  Little_corn  阅读(13)  评论(0编辑  收藏  举报