題意
定義一種長度爲𝑛的排列,滿足任意兩個相鄰的數的和都≤ 𝑚,現在要求你
計算它的個數,記爲𝑆(𝑛, 𝑚)。
但是出題人覺得這道題不足以讓人提起興趣去做,於是稍作修改,給定𝑛, 𝑘
要求你計算:
答案對 取模。
題解
顯然當時和式的每一項都是,答案就是階乘和。但範圍都是,怎麼算階乘呢?可以發現,當時,。那麼大於的都不用計算,直接預處理內的就行了。
考慮怎麼計算。令相當於考慮在時如何計算。
對於範圍內的數,它們的排列順序並不影響答案,因爲其中最大的加上也小於等於。因此他們內部的偶愛列順序方案就是。
再考慮的數放進來。我們的順序是先放,再放,再放,再放。也就是從兩頭一邊輪流放一個。
那麼放的時候有種方案(已經有個數,插一個數進去),然後放時本該有種方案,但不能放在兩邊,所以要減,結果就是種方案。
那麼這樣插入了兩個數,下一次放時由於不能和放在一起,所以減,方案數也是;同樣的方案也是。
所以總方案就是和很多個和很多個(j-i)乘起來。具體答案是:
也就是
那麼要求和,發現定,分奇偶性可以變成兩個等比數列求和。然後就完事了。
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);
}
}