PSequence 题解 DP

PSequence

题目描述

给定一个元素集合 S S S,求 S S S 的所有排列满足对于任意相邻两个元素 s 1 s_1 s1​, s 2 s_2 s2 ( s 1 − s 2 ) (s_1−s_2) (s1s2) 不被 P 整除。保证 S S S 中任意两个元素都不相同。

输入格式

输入第一行一个数 n n n,表示集合 S S S 的大小

以下 n n n 个数

最后一个数 P P P

n
s1 s2 ... sn
P

输出格式

输出一个数,表述满足条件的排列的个数,模 1234567891 1234567891 1234567891

样例

input #1

4
1 2 3 4
3

output #1

12

数据范围及提示

对于 30 % 30\% 30% 的数据, n ≤ 9 n\leq 9 n9
对于 100 % 100\% 100% 的数据, n ≤ 30 n\leq 30 n30 S i ≤ 1 0 6 S_i\leq 10^6 Si106

注明

转载需经过本人同意。 转载需经过本人同意。 转载需经过本人同意。

解题思路

首先,若 s i s_i si s i + 1 s_{i+1} si+1 如果不符合条件,则 s 1 s_1 s1 s 2 s_2 s2 p p p 同。那么你用 C C C 数组可以记下 S S S 内所有元素的值模 p p p 等于的各个数的个数,从大到小排序。设要在原序列选 k k k 个不合法, l l l 个合法的的位置插入,设 F i , j F_{i,j} Fi,j 表示处理了前 i i i 大的余数的数,有 j j j 对不合法的方案数的方案数。不难推出:
F i , j − k + C i − k − 1 + = F i − 1 , j × C j k × C s + 1 − j l × C c − 1 k + l − 1 F_{i,j-k+C_i-k-1}+=F_{i-1,j}\times C_j^k\times C_{s+1-j}^l\times C_{c-1}^{k+l-1} Fi,jk+Cik1+=Fi1,j×Cjk×Cs+1jl×Cc1k+l1

当枚举到 C C C 数组的值为 0 0 0 时,答案就为: F i − 1 , 0 × Π C i ! F_{i-1,0}\times \Pi C_i! Fi1,0×ΠCi!

Code

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int Maxn = 30 + 5, Maxval = 1e6 + 5, Mod = 1234567891;
int Fac[Maxn], InFac[Maxn];
inline int Q_Pow(int x, int y) {
	int res = 1;
	while (y) {
		if (y & 1)
			res = res * x % Mod;
		x = x * x % Mod;
		y >>= 1;
	}
	return res;
}
inline int Get_C(int a, int b) {
	if (b > a || a < 0 || b < 0)
		return 0;
	return Fac[a] * InFac[b] % Mod * InFac[a - b] % Mod;
}
int n, s[Maxn], p;
map<int, int>Map;
int C[Maxval], Top, F[Maxn][Maxn];
int Answer;
inline bool Cmp(int a, int b) {
	return a > b;
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	Fac[0] = 1;
	InFac[0] = 1;
	for (register int i = 1; i < Maxn; ++i) {
		Fac[i] = Fac[i - 1] * i % Mod;
		InFac[i] = InFac[i - 1] * Q_Pow(i, Mod - 2) % Mod;
	}
	cin >> n;
	for (register int i = 1; i <= n; ++i)
		cin >> s[i];
	cin >> p;
	for (register int i = 1; i <= n; ++i)
		++Map[(s[i] % p + p) % p];
	for (auto [x, y] : Map)
		C[Top++] = y;
	sort(C, C + Top, Cmp);
	F[0][C[0] - 1] = 1;
	for (register int i = 1, s = C[0]; i <= n; ++i) {
		if (C[i] == 0) {
			for (register int j = 0; j < Top; ++j)
				(F[i - 1][0] *= Fac[C[j]]) %= Mod;
			cout << F[i - 1][0] << endl;
			exit(0);
		}
		for (register int j = 0; j <= s; ++j)
			for (register int k = 0; k <= C[i]; ++k)
				for (register int l = 0; l <= C[i]; ++l)
					if (j - k + C[i] - k - l >= 0)
						(F[i][j - k + C[i] - k - l] += F[i - 1][j] * Get_C(j, k) % Mod * Get_C(s + 1 - j, l) % Mod * Get_C(C[i] - 1, k + l - 1) % Mod) %= Mod;
		s += C[i];
	}
	return 0;
}

然后你就秒了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值