Subpermutation-2021中国大学生程序设计竞赛(CCPC)- 网络选拔赛(重赛)

题意:
把所有 n n n元排列按字典序从小到大排成一列,求其中有多少子串为 m m m元排列, t t t组询问。
题解:
(1)考虑单个 n n n元组中的 m m m元组个数。
m m m元组看成一个元素,排列组合有 m ! m! m!的情况,现在共有 ( n − m + 1 ) (n-m+1) (nm+1)个元素,排列组合为 ( n − m + 1 ) ! (n-m+1)! (nm+1)!。共有 m ! ( n − m + 1 ) ! m!(n-m+1)! m!(nm+1)! m m m元组。
(2)考虑跨两个 n n n元组的 m m m元组个数。
假设 k k k为某个排列中的最后一个使得 p k < p k + 1 > p k + 2 p_k<p_{k+1}>p{k+2} pk<pk+1>pk+2的位置。
p 1 , p 2 , p 3 . . . p k < p k + 1 > p k + 2 > p k + 3 . . . > p n p_1,p_2,p_3...p_k<p_{k+1}>p_{k+2}>p_{k+3}...>p_n p1,p2,p3...pk<pk+1>pk+2>pk+3...>pn
那么下一个字典序的排列为:
p 1 , p 2 , p 3... p j < p n < . . . < p j + 1 < p k < p j − 1 < . . . . < p k + 1   ( p j > p k ) p1,p2,p3...p_j<p_n<...<p_{j+1}<p_k<p_{j-1}<....<p_{k+1}\ (p_j>p_k) p1,p2,p3...pj<pn<...<pj+1<pk<pj1<....<pk+1 (pj>pk)
假设 i i i为我们所要的 m m m元组开头的位置。

1.当 i < = k i<=k i<=k时:

第一个排列中存在 n − i + 1 n-i+1 ni+1个数,剩下的数在第二排列中,因为 m < n m<n m<n,所以有 m − ( n − i + 1 ) < k m-(n-i+1)<k m(ni+1)<k
因为 i < = k i<=k i<=k所以在 [ i , n ] [i,n] [i,n]内必须存在一个位置 k k k使得 p k < p k + 1 p_k<p_{k+1} pk<pk+1。方案数转化为全部的方案减去 p i > p i + 1 > . . . > p n p_i>p_{i+1}>...>p_n pi>pi+1>...>pn的方案数。
也就是 m ! ( 全 部 的 方 案 ) − ∁ m n − i + 1 ( m − ( n − i + 1 ) ) !   ( m 个 数 选 出 n − i + 1 个 数 在 第 一 个 排 列 中 , 且 为 降 序 排 列 的 方 案 ) m!(全部的方案)-\complement_m^{n-i+1}(m-(n-i+1))!\ (m个数选出n-i+1个数在第一个排列中,且为降序排列的方案) m!()mni+1(m(ni+1))! (mni+1,)
剩下的 n − m n-m nm个数随便排列。
于是此时的方案数为 ( n − m ) ! ( m ! − ∁ m n − i + 1 ( m − ( n − i + 1 ) ) ! ) (n-m)!(m!-\complement_m^{n-i+1}(m-(n-i+1))!) (nm)!(m!mni+1(m(ni+1))!)

2.当 i > = k + 1 i>=k+1 i>=k+1时:

此时 p k p_k pk不在 m m m元组中, p k > m p_k>m pk>m,而 p j > p k > m p_j>p_k>m pj>pk>m,所以 p j p_j pj也不再 m m m元组中。
同样:(标黄的是m元组出现的范围)
p 1 , p 2 , p 3 . . . p k < p k + 1 > p_1,p_2,p_3...p_k<p_{k+1}> p1,p2,p3...pk<pk+1> p k + 2 > p k + 3 . . . > p n p_{k+2}>p_{k+3}...>p_n pk+2>pk+3...>pn
那么下一个字典序的排列为:
p 1 , p 2 , p 3 p1,p2,p3 p1,p2,p3 . . . p j < p n < . . . < p j + 1 < p k < p j − 1 < . . . . < p k + 1   ( p j > p k ) ...p_j<p_n<...<p_{j+1}<p_k<p_{j-1}<....<p_{k+1}\ (p_j>p_k) ...pj<pn<...<pj+1<pk<pj1<....<pk+1 (pj>pk)
这就意味着在第二个排列中,最后一个元素的下标为 i + m − 1 − n < j i+m-1-n<j i+m1n<j
n − i + 1 n-i+1 ni+1个数是递减的,剩下 ( m − ( n − i + 1 ) ) (m-(n-i+1)) (m(ni+1))个数没有限制,方案数为 ( m − ( n − i + 1 ) ) ! (m-(n-i+1))! (m(ni+1))!。对于 m m m元组以外的 m − n m-n mn个数,必须存在一个 k k k使得 p k < p k + 1 p_k<p_{k+1} pk<pk+1,方案数为 ( ( n − m ) ! − 1 ) ((n-m)!-1) ((nm)!1)

i i i的范围:
第一个排列中的数要小于 m m m.
( n − i + 1 ) > = m − 1 , i > = n + 2 − m (n-i+1)>=m-1,i>=n+2-m (ni+1)>=m1,i>=n+2m

最后答案就是 m ! ( n − m + 1 ) ! + ∑ i = n + 2 − m n ( ( n − m ) ! ( m ! − ∁ m n − i + 1 ( m − ( n − i + 1 ) ) ! ) + ∁ m n − i + 1 ( m − ( n − i + 1 ) ) ! ( ( n − m ) ! − 1 ) ) m!(n-m+1)!+\sum_{i=n+2-m}^n((n-m)!(m!-\complement_{m}^{n-i+1}(m-(n-i+1))!) +\complement_{m}^{n-i+1}(m-(n-i+1))!((n-m)!-1)) m!(nm+1)!+i=n+2mn((nm)!(m!mni+1(m(ni+1))!)+mni+1(m(ni+1))!((nm)!1))
化简得: m ! ( n − m + 1 ) ! + ( m − 1 ) ( n − m ) ! m ! − m ! ∑ i = 1 m − 1 1 i ! m!(n-m+1)!+(m-1)(n-m)!m!-m!\sum_{i=1}^{m-1}\frac{1}{i!} m!(nm+1)!+(m1)(nm)!m!m!i=1m1i!1

#include <cstdio>
#include <cmath>
#include <algorithm>
#include <iostream>
#include <cstring>
#include <map>
#include <string>
#include <stack>
#include <cctype>
#include <vector>
#include <queue>
#include <set>
#include <utility>
#include <cassert>
#include <iomanip>
#include <deque>
#include <time.h>
#include <bitset>
using namespace std;
#define ll long long
#define maxn 1000005
#define mod 1000000007
#define MOD 998244353
#define Mod 1000000009
#define eps 1e-10
const ll inf=0x3f3f3f3f3f3f3f3f;
const ll INF=0x3f3f3f3f;
const ll mod1=1e9+7;
const ll mod2=1e9+9;
template <typename T>
inline void read(T& X) {X = 0; int w = 0; char ch = 0;while (!isdigit(ch)) { w |= ch == '-'; ch = getchar(); }while (isdigit(ch)) X = (X << 3) + (X << 1) + (ch ^ 48), ch = getchar();if (w) X = -X;}
char F[200];inline void write(int x){if(x == 0){putchar('0');return;}int tmp = x > 0 ? x : -x;int cnt = 0;if(x < 0)putchar( '-' );while(tmp > 0){F[cnt++] = tmp % 10 + '0';tmp /= 10;}while(cnt > 0)putchar(F[--cnt]) ;}
template<typename T> void print(T x){if(x>9) print(x/10);putchar(x%10+'0');}
ll q_pow(ll x,ll y,ll M){ll ans=1;while(y){if(y%2){y--;ans=ans*x%M;}else {y/=2;x=x*x%M;}}return ans;}
ll a[maxn],b[maxn],f[maxn];
void init(){
	a[0]=1;
	for(ll i=1;i<=1000000;i++)a[i]=a[i-1]*i%mod;
	b[1000000]=q_pow(a[1000000],mod-2,mod);
	for(ll i=999999;i>=0;i--)b[i]=b[i+1]*(i+1ll)%mod;
	for(int i=1;i<=1000000;i++)f[i]=(f[i-1]+b[i])%mod;
}
ll C(ll n,ll m){
	return a[n]*b[m]%mod*b[n-m]%mod;
}
int main() 
{
	ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
	int t;
	ll n,m;
	init();
	cin>>t;
	while(t--){
		cin>>n>>m;
		ll ans=a[m]*a[n-m+1]%mod+(m-1)*a[n-m]%mod*a[m]%mod-a[m]*f[m-1]%mod;
		ans=(ans%mod+mod)%mod;
		cout<<ans<<"\n";
	}
    return 0;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值