NOI模拟赛 T1.permutation 排列 (计数)
题意
定义一种长度为𝑛的排列,满足任意两个相邻的数的和都≤ 𝑚,现在要求你
计算它的个数,记为𝑆(𝑛, 𝑚)。
但是出题人觉得这道题不足以让人提起兴趣去做,于是稍作修改,给定𝑛, 𝑘
要求你计算:
答案对 \(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)乘起来。具体答案是:
也就是
那么要求和,发现\(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);
}
}