NOI模拟赛 T1.permutation 排列 (计数)

题意

定义一种长度为𝑛的排列,满足任意两个相邻的数的和都≤ 𝑚,现在要求你
计算它的个数,记为𝑆(𝑛, 𝑚)。

但是出题人觉得这道题不足以让人提起兴趣去做,于是稍作修改,给定𝑛, 𝑘
要求你计算:

\[\sum_{i=1}^nS(i,i+k) \]

答案对 \(20000023\) 取模。

题解

显然当\(n\le k\)时和式的每一项都是\(i!\),答案就是阶乘和。但\(n,k\)范围都是\(1e18\),怎么算阶乘呢?可以发现,当\(x\ge mod\)时,\(x!\equiv0\)。那么大于\(mod\)的都不用计算,直接预处理\(mod\)内的就行了。

考虑\(i>k\)怎么计算。令\(j=i+k\)相当于考虑\(S(i,j)\)\(i<j<2i\)时如何计算。

对于\([1,j-i]\)范围内的数,它们的排列顺序并不影响答案,因为其中最大的\(j-i\)加上\(i\)也小于等于\(j\)。因此他们内部的偶爱列顺序方案就是\((j-i)!\)

再考虑\([j-i+1,i]\)的数放进来。我们的顺序是先放\(j-i+1\),再放\(i\),再放\(j-i+2\),再放\(i-1\)。也就是从两头一边轮流放一个。

那么放\(j-i+1\)的时候有\(j-i+1\)种方案(已经有\(j-i\)个数,插一个数进去),然后放\(i\)时本该有\(j-i+2\)种方案,但不能放在\(j-i+1\)两边,所以要减\(2\),结果就是\(j-i\)种方案。

那么这样插入了两个数,下一次放\(j-i+2\)时由于不能和\(i\)放在一起,所以减\(2\),方案数也是\(j-i+1\);同样\(i-1\)的方案也是\(j-i\)

所以总方案就是\((j-i)!\)和很多个\((j-i+1)\)和很多个(j-i)乘起来。具体答案是:

\[S(i,j)=(j-i)!(j-i+1)^{\lceil\frac{2i-j}2\rceil}(j-i)^{\lfloor\frac{2i-j}2\rfloor} \]

也就是

\[S(i,i+k)=k!(k+1)^{\lceil\frac{i-k}2\rceil}k^{\lfloor\frac{i-k}2\rfloor} \]

那么要求和,发现\(k\)定,分奇偶性可以变成两个等比数列求和。然后就完事了。

CODE

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int MAXN = 20000025;
const int mod = 20000023;
int fac[MAXN], sum[MAXN], ans;
LL n, k;
inline int qpow(int a, int b) {
	int re = 1;
	while(b) {
		if(b&1) re = 1ll * re * a % mod;
		a = 1ll * a * a % mod; b >>= 1;
	}
	return re;
}
int main () {
	freopen("permutation.in", "r", stdin);
	freopen("permutation.out", "w", stdout);
	fac[0] = 1; sum[0] = 0;
	for(int i = 1; i <= mod; ++i) fac[i] = 1ll * fac[i-1] * i % mod, sum[i] = (sum[i-1] + fac[i]) % mod;
	int T; scanf("%d", &T);
	while(T--) {
		scanf("%lld%lld", &n, &k);
		int ans = sum[min(min(n, k), (LL)mod)]; //前面一段用于处理好的阶乘
		int s = 0, w = 1ll*k*(k+1)%mod, inv = qpow(w-1+mod, mod-2) % mod; //inv是等比数列求和公式的分母
		if(n > k && k < mod) //k>=mod时因为后面要乘k的阶乘,而k的阶乘肯定模出来是0,就没必要算
			s = (s + 1ll*(k+1)*(qpow(w, ((n-k-1)/2+1)%(mod-1))-1)%mod*inv) % mod; //这个等比数列是从w^0+w^1+...+w^(n-k-1)/2,-1是等比数列求和公式中需要的.
		if(n > k+1 && k < mod)
			s = (s + 1ll*(qpow(w, ((n-k)/2+1)%(mod-1))-1)*inv - 1) % mod; //这个等比数列是从w^1+w^2+...+w^(n-k)/2,所以除去等比公式中的一个-1,后面多减了-1.
		ans = (ans + 1ll * s * fac[min(k,(LL)mod)]) % mod;
		printf("%d\n", ans);
	}
}
posted @ 2020-01-02 22:16  _Ark  阅读(146)  评论(0编辑  收藏  举报