【組合數】Subpermutation-[2021中國大學生程式設計競賽(CCPC)- 網路選拔賽(重賽)]
題目連結(http://acm.hdu.edu.cn/showproblem.php?pid=7133)
Time Limit: 2000/1000 MS (Java/Others)
Memory Limit: 262144/262144 K (Java/Others)
Problem Description
A permutation of \(n\) is a sequence of length \(n\) in which each number from \(1\) to \(n\) appears exactly once. A \(\textit{full-permutation}\) of \(n\) is a sequence that connects all permutations of \(n\)
Here are some symbols used in this problem:
- \(p_n\): the full-permutation of \(n\)
- \(S_n\): the set of all permutations of \(n\). For example, \(S_3=\{\{1,2,3\},\{1,3,2\},\{2,1,3\},\{2,3,1\},\{3,1,2\},\{3,2,1\}\}\).
- \(f(s,t)\): the number of contiguous subsequences in \(s\) that are equal to \(t\). For example, \(f(\{1,2,12,1,2\},\{1,2\})=2\)
Now given \(n\) and \(m\), please calculate \(\sum_{t\in{S_m}}f(p_n,t)\) modulo \(10^9+7\).
Input
The first line contains one integer \(T\ (1\le T\le 10^5)\), indicating the number of test cases.
The only line for each case contains two integers \(n\ (1\le n\le 10^6)\) and \(m\ (1\le m\le n)\), as described in the description.
Output
For each test case, output a single integer \(\sum_{t\in{S_m}}f(p_n,t)\) modulo \(10^9+7\).
Sample Input
4
2 1
2 2
3 2
4 3
Sample Output
2
2
4
15
Hint
For the third case in the sample, \(p_3 = \{1,2,3,1,3,2,2,1,3,2,3,1,3,1,2,3,2,1\}\), \(S_2=\{\{1,2\},\{2,1\}\}\). There are \(4\) contiguous subsequences in \(p_3\) that are equal to \(\{1,2\}\) or \(\{2,1\}\): \(\{\underline{1,2},3,1,3,2,\underline{2,1},3,2,3,1,3,\underline{1,2},3,\underline{2,1}\}\).
題意
把所有\(n\)元排列按字典序從小到大排成一列,求其中有多少子串為\(m\)元{1 ~ m}排列,模\(1e9+7\),\(t\)組詢問,(\(T ≤ 10 ^ 5,1 ≤m≤n≤10^6\))
思路
程式碼很簡短,思路清楚比較重要,補題怕自己忘了所以記錄一下(來自emo老師講題,自己做的筆記)
分解子問題:
子問題一:在一個完整的\(n\)元排列裡面有\(m\)元排列為子串的個數
假設\(p_1,p_2……p_n\)為\(n\)元排列,裡面有一個子串是\(m\)元排列。\(m\)元排列的元素緊湊在一起,所以它自己的元素的排列方式有\(m!\)種,把它看成一個小團。{1~m}共包含\(m\)個數,剩下來的數共\(n-m\)個,剩下的數各為小團,把所有小團進行一個排列,所以總排列數為 \(m\)元排列小團的排列數 * 所有小團的排列數,即 \(m! * (n - m + 1) !\)
子問題二(難點):\(m\)元排列有可能穿插在了兩個相鄰的\(n\)元排列中
\(e.g.\) \(n = 4,m = 4\)
排列{\(1,2,\underline{3,4,1,2,}4,3 ......\)} 或者{\(1,\underline{2,3,4,1,}2,4,3 ......\)} 中前兩個\(n\)元排列中也出現了合法的\(m\)元排列
這時候就要思考一下兩個相鄰的\(n\)元排列有什麼性質
隨便列舉幾個相鄰排列
{\(1,2,3,4\)}{\(1,2,4,3\)} {\(2,4,1,3\)}{\(2,4,3,1\)}
我們會發現對於\(n\)元排列 \(p_1,p_2 ... p_n\) 可以找到
\(p_1,p_2,...p_k < p_{k + 1} > p_{k + 2} > ... > p_n\)(\(k\)是最後一個滿足\(p_k < p_{k + 1}\) 的下標)
它的下一個排列中的\(p_k\),會被原排列中的一個\(p_j\) (\(p_j > p_k\))替換掉,後面的元素倒著排列
那麼下一個排列為
\(p_1,p_2,...,p_{k-1}, p_j,p_n,p_{n-1}, ...p_{j+1} ,p_k,p_{j-1},...p_{k+1}\)
它符合的性質是
\(p_1,p_2,...,p_{k-1}, p_j>p_n<p_{n-1}< ... <p_{j+1} <p_k<p_{j-1}<...<p_{k+1}\)
如果一個\(m\)元排列插在兩個\(n\)元排列中間,從下標\(i\)開始,
①(\(i≤k\)) 該排列構造:\(p_i,...,p_k,p_{k + 1},p_{k + 2},...,p_j,...,p_n,p_1,p_2...p_{i + m - 1 - n}\)
思考這裡有多少種排列方式
首先剩下的\(n-m\)個元素隨便排,這部分是\((n - m)!\),\(m\)元序列裡\(1\)到\(m\)本應隨便排,但要求\(1\)到\(m\)在前一個\(n\)元排列中,一定要出現一個\(p_k<p_{k + 1}\)這樣的數對才行,否則不合法,所以剩下的部分是\(m!\)減去不合法的情況,即減去\(p_i>p_{i + 1} > ... >p _ n\)這種情況,即\(m! - C^m_{n-i+1} * [m-(n-i+1)]!\)
所以方案數 = \((n - m)!*(m! - C^m_{n-i+1} * [m-(n-i+1)]!)\)
②(\(i ≥k+1\))該排列構造:\(p_i\)從\(p_i≥p_{k+1}\)的位置到下一個序列 (不敲了,熒光筆範圍)
首先\(1\)到\(m\)一定是\(1\)到\(n\)裡面最小的\(m\)個數,對這兩個序列進行分析,發現\(p_j\)要大於\(p_k\),所以如果\(p_j\)在\(1\)到\(m\)的序列裡那\(p_k\)必定在裡面,這和\(p_i\)的範圍不符,因而只有一種可能就是\(p_j\)不在\(1\)到\(m\)的序列裡,則最後一位的下標\((i+m-1-n) < k\),故這一情況的排列方式可以像上一種那樣算出來
首先\(1\)到\(m\)怎麼選,第一個排列裡,規則同上,為\(C_m^{n-i +1} * [m - (n - i + 1)] !\)
剩下的數裡,由於一定要包含\(p_k < p_{k +1}\),所以剩下的數不能完全呈單調遞減,需要減去單調遞減一情況,為\(((n-m)!-1)\)
所以方案數 = \(C_m^{n-i +1} * [m - (n - i + 1)] ! * ((n-m)!-1)\)
總結來說,橫跨兩個\(n\)元排列的方案數為\(\sum_{n - m + 2≤i≤n} (n - m)! * (m ! - C_m^{n - i +1} * (m - (n - i + 1))!+ C_m^{n - i + 1} * (m - (n -i +1))! * ((n - m)! - 1)\)
再進行一些推導,發現
這兩個顏色不同的地方是可以相互抵消的
那麼化簡就可得方案數 = \(\sum_{i = n - m + 2} ^ n m! * (n - m)! - C_m^{n - i + 1}*(m - (n - i + 1))!\)
= \(\sum_{i = n - m + 2} ^ n m! * (n - m)! - m!/(n - i + 1) !\)
= \((m-1) * m! * (n - m) !-m! *\sum_{i = n - m + 2} ^ n 1/(n - i + 1)!\)
= \((m-1) * m! * (n - m) !-m! *\sum_{i = 1} ^ {m - 1} 1/i!\)
這樣,預處理之後,O(1)即可得橫跨兩個\(n\)元排列的方案數
而一個排列內的方案數,預處理下階乘即可,兩種排列方式的方法相加即得答案
AC程式碼
#include <bits/stdc++.h>
#define inf 0x3f3f3f3f
#define llinf 0x3f3f3f3f3f3f3f3f
#define int long long
#define ull unsigned long long
#define PII pair<int,int>
using namespace std;
typedef long long ll;
const int N = 1e6 + 100;
const int mod = 1e9 + 7;
int t, n, m;
int a[N];
int fact[N], invfact[N];
int quickpow(int a, int b) {//快速冪板子
int res = 1;
while (b > 0) {
if (b & 1) res = res * a % mod;
b >>= 1;
a = a * a % mod;
}
return res;
}
void init() {//預處理
fact[0] = 1;
for (int i = 1; i < 1000001; i++) {
fact[i] = fact[i - 1] * i % mod;
}
invfact[1000000] = quickpow(fact[1000000], mod - 2);
for (int i = 1000000; i; i--) {
invfact[i - 1] = invfact[i] * i % mod;
}
for (int i = 1; i < 1000001; i++) {
invfact[i] = (invfact[i - 1] + invfact[i]) % mod;
}
return;
}
void solve() {
cin >> n >> m;//輸入 n m
cout << (fact[m] * (n * fact[n - m] % mod - invfact[m - 1] + invfact[0] + mod) % mod) << endl;//直接輸出公式求得答案
return;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
init();
while (cin >> t) {
while (t--) {
solve();
}
}
return 0;
}
/*
i raised a cute kitty in my code,
my friend who pass by can touch softly on her head:)
/l、
Meow~(゚、 。7
|、 ~ヽ
じしf_,)ノ
*/