【组合数】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 full-permutation of n is a sequence that connects all permutations of n into one sequence in lexicographical order. Sequence p1,p2,,pn is lexicographically smaller than q1,q2,,qn if pi<qi where i is the minimum index satisfying piqi.

Here are some symbols used in this problem:

- pn: the full-permutation of n. For example, p3={1,2,3,1,3,2,2,1,3,2,3,1,3,1,2,3,2,1} .
- Sn: the set of all permutations of n. For example, S3={{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 tSmf(pn,t) modulo 109+7.

Input

The first line contains one integer T (1T105), indicating the number of test cases.

The only line for each case contains two integers n (1n106) and m (1mn), as described in the description.

Output

For each test case, output a single integer tSmf(pn,t) modulo 109+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, p3={1,2,3,1,3,2,2,1,3,2,3,1,3,1,2,3,2,1}, S2={{1,2},{2,1}}. There are 4 contiguous subsequences in p3 that are equal to {1,2} or {2,1}: {1,2_,3,1,3,2,2,1_,3,2,3,1,3,1,2_,3,2,1_}.

题意

把所有n元排列按字典序从小到大排成一列,求其中有多少子串为m元{1 ~ m}排列,模1e9+7t组询问,(T105,1mn106

思路

代码很简短,思路清楚比较重要,补题怕自己忘了所以记录一下(来自emo老师讲题,自己做的笔记)

分解子问题:

子问题一:在一个完整的n元排列里面有m元排列为子串的个数

假设p1,p2pnn元排列,里面有一个子串是m元排列。m元排列的元素紧凑在一起,所以它自己的元素的排列方式有m!种,把它看成一个小团。{1~m}共包含m个数,剩下来的数共nm个,剩下的数各为小团,把所有小团进行一个排列,所以总排列数为 m元排列小团的排列数 * 所有小团的排列数,即 m!(nm+1)!

子问题二(难点):m元排列有可能穿插在了两个相邻的n元排列中

e.g. n=4,m=4

排列{1,2,3,4,1,2,_4,3......} 或者{1,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元排列 p1,p2...pn 可以找到

p1,p2,...pk<pk+1>pk+2>...>pnk是最后一个满足pk<pk+1 的下标)

它的下一个排列中的pk,会被原排列中的一个pj (pj>pk)替换掉,后面的元素倒着排列

那么下一个排列为

p1,p2,...,pk1,pj,pn,pn1,...pj+1,pk,pj1,...pk+1

它符合的性质是

p1,p2,...,pk1,pj>pn<pn1<...<pj+1<pk<pj1<...<pk+1

如果一个m元排列插在两个n元排列中间,从下标i开始,

①(ik) 该排列构造:pi,...,pk,pk+1,pk+2,...,pj,...,pn,p1,p2...pi+m1n

思考这里有多少种排列方式

首先剩下的nm个元素随便排,这部分是(nm)!,m元序列里1m本应随便排,但要求1m在前一个n元排列中,一定要出现一个pk<pk+1这样的数对才行,否则不合法,所以剩下的部分是m!减去不合法的情况,即减去pi>pi+1>...>pn这种情况,即m!Cni+1m[m(ni+1)]!

所以方案数 = (nm)!(m!Cni+1m[m(ni+1)]!)

②(ik+1)该排列构造:pipipk+1的位置到下一个序列 (不敲了,荧光笔范围)

首先1m一定是1n里面最小的m个数,对这两个序列进行分析,发现pj要大于pk,所以如果pj1m的序列里那pk必定在里面,这和pi的范围不符,因而只有一种可能就是pj不在1m的序列里,则最后一位的下标(i+m1n)<j,故这一情况的排列方式可以像上一种那样算出来

首先1m怎么选,第一个排列里,规则同上,为Cni+1m[m(ni+1)]!

剩下的数里,由于一定要包含pk<pk+1,所以剩下的数不能完全呈单调递减,需要减去单调递减一情况,为((nm)!1)

所以方案数 = Cni+1m[m(ni+1)]!((nm)!1)

总结来说,横跨两个n元排列的方案数为nm+2in(nm)!(m!Cmni+1(m(ni+1))!+Cmni+1(m(ni+1))!((nm)!1)

再进行一些推导,发现

这两个颜色不同的地方是可以相互抵消的

那么化简就可得方案数 = i=nm+2nm!(nm)!Cmni+1(m(ni+1))!

= i=nm+2nm!(nm)!m!/(ni+1)!

= (m1)m!(nm)!m!i=nm+2n1/(ni+1)!

= (m1)m!(nm)!m!i=1m11/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_,)ノ

*/
posted @   TomiokapEace  阅读(340)  评论(2编辑  收藏  举报
编辑推荐:
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
点击右上角即可分享
微信分享提示