给你一个长度为 $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#include #define N 1010#define M 5010#define mod 998244353using 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;}