【uoj#22】[UR #1]外星人 组合数学+dp

题目描述

给你一个长度为 $n$ 的序列 $\{a_i\}$ 和一个数 $x$ ,对于任意一个 $1\sim n$ 的排列 $\{p_i\}$ ,从 $1$ 到 $n$ 依次执行 $x=x\ \text{mod}\ a_{p_i}$ ,最终得到一个数。求所有排列中能够得到的这个数的最大值,以及有多少种排列可以得到这个值。

$n\le 1000$ ,$x\le 5000$ 。


题解

组合数学+dp

由于 $a\ \text{mod}\ b<b$ ,因此每次产生影响(即 $x\ \text{mod}\ a_i\ne x$)的 $a_i$ 一定是递减的。

考虑将所有数从大到小排序处理。当确定了某个要产生影响的模数 $a_i$ ,$(x\ \text{mod}\ a_i,x]$ 中除了 $a_i$ 的数就都可以随便放在 $a_i$ 的后面,因为它们不会产生贡献。

设 $f[i]$ 表示处理完 $(i,+\infty)$ 的数,剩下的数为 $i$ 的方案数。

那么考虑 $f[i]$ ,枚举下一个选择的数 $j$ ,令 $s[i]$ 表示小于等于 $i$ 的数的个数,则有 $f[i\ \text{mod}\ j]=f[i]\times A_{s[i]-1}^{s[i]-1-s[i\ \text{mod}\ j]}=f[i]\times\frac{(s[i]-1)!}{(s[i\ \text{mod}\ j])!}$ 。预处理阶乘及阶乘的逆元即可。

时间复杂度 $O(n^2)$ 。

#include <cstdio>
#include <algorithm>
#define N 1010
#define M 5010
#define mod 998244353
using namespace std;
typedef long long ll;
int a[N] , sum[M];
ll fac[N] , inv[N] , fin[N] , f[M];
int main()
{
	int n , m = 5000 , x , i , j , mn = m;
	scanf("%d%d" , &n , &x);
	for(i = 1 ; i <= n ; i ++ ) scanf("%d" , &a[i]) , sum[a[i]] ++ , mn = min(mn , a[i]);
	for(i = 1 ; i <= m ; i ++ ) sum[i] += sum[i - 1];
	fac[0] = fin[0] = fac[1] = inv[1] = fin[1] = 1;
	for(i = 2 ; i <= n ; i ++ )
	{
		fac[i] = fac[i - 1] * i % mod;
		inv[i] = (mod - mod / i) * inv[mod % i] % mod;
		fin[i] = fin[i - 1] * inv[i] % mod;
	}
	f[x] = fac[n] * fin[sum[x]] % mod;
	for(i = m ; i ; i -- )
		for(j = 1 ; j <= n ; j ++ )
			if(a[j] <= i)
				f[i % a[j]] = (f[i % a[j]] + f[i] * fac[sum[i] - 1] % mod * fin[sum[i % a[j]]]) % mod;
	for(i = mn - 1 ; ~i ; i -- )
		if(f[i])
			break;
	printf("%d %lld" , i , f[i]);
	return 0;
}

 

posted @ 2018-03-21 16:38  GXZlegend  阅读(526)  评论(0编辑  收藏  举报