[Luogu P4071] [BZOJ 4571] [SDOI2016] 排列计数

22 篇文章 0 订阅
2 篇文章 0 订阅
洛谷传送门
BZOJ传送门

题目描述

求有多少种长度为 n n 的序列 A,满足以下条件:

1n 1 ∼ n 这 n 个数在序列中各出现了一次

若第 i i 个数 A[i] 的值为 i i ,则称 i 是稳定的。序列恰好有 m m 个数是稳定的

满足条件的序列可能很多,序列数对 109+7 取模。

输入输出格式

输入格式:

第一行一个数 T T ,表示有 T 组数据。

接下来 T T 行,每行两个整数 n m m

输出格式:

输出 T 行,每行一个数,表示求出的序列数

输入输出样例

输入样例#1:
5
1 0
1 1
5 2
100 50
10000 5000
输出样例#1:
0
1
20
578028887
60695423

说明

测试点 1 ~ 3: T=1000 T = 1000 n8 n ≤ 8 m8 m ≤ 8

测试点 4 ~ 6: T=1000 T = 1000 n12 n ≤ 12 m12 m ≤ 12

测试点 7 ~ 9: T=1000 T = 1000 n100 n ≤ 100 m100 m ≤ 100

测试点 10 ~ 12: T=1000 T = 1000 n1000 n ≤ 1000 m1000 m ≤ 1000

测试点 13 ~ 14: T=500000 T = 500000 n1000 n ≤ 1000 m1000 m ≤ 1000

测试点 15 ~ 20: T=500000 T = 500000 n1000000 n ≤ 1000000 m1000000 m ≤ 1000000

解题分析

前置知识 乘法逆元

首先我们有 inv[1]=1 i n v [ 1 ] = 1

N=i×k+p N = i × k + p

放到 mod N m o d   N 意义下就有 i×k+p=0 i × k + p = 0

同乘 inv[i] i n v [ i ] inv[p] i n v [ p ] 可得

k× inv[p]+inv[i]=0 k ×   i n v [ p ] + i n v [ i ] = 0

可得 inv[i]=k× inv[p] i n v [ i ] = − k ×   i n v [ p ]

inv[i]=N/i×inv[N mod i] i n v [ i ] = − ⌊ N / i ⌋ × i n v [ N   m o d   i ]

我们就有了 O(N) O ( N ) 线筛逆元的方法了。

下面看这道题:
首先一眼看出我们可以钦定 m m 个点为稳定点, 所以答案先乘一个Cnm

对于剩下的 nm n − m 个点,设有 k k 个点时方案数为val[k],我们考虑从当前有 k1 k − 1 位向 k k 位递推:

首先我们肯定要将另一位p和第 k k 位上的数(我们默认为k)互换一下, 这样换有k1种方法。

然后对于每种方法我们就有 val[k1] v a l [ k − 1 ] 种构造方案。(前 k1 k − 1 位排列即可)

这样就完了吗?

显然不是的。 在 val[k1] v a l [ k − 1 ] 中我们只考虑了第 i i 位不能为i的情况, 但我们换入了一个不会重复的值 k k ,导致第p位上可以放 k k 。(感觉讲的好迷啊QAQ)

考虑放k在第 p p 位上的情况。其实就等同于前k2位排列,且不会存在上述情况。

所以我们得到递推公式:

val[k]=(k1)×(val[k1]+val[k2]) v a l [ k ] = ( k − 1 ) × ( v a l [ k − 1 ] + v a l [ k − 2 ] )

边界条件位 val[0]=1 v a l [ 0 ] = 1 val[1]=0 v a l [ 1 ] = 0 val[2]=1 v a l [ 2 ] = 1

然后我们就可以筛出逆元、阶乘、阶乘逆元以及 val[] v a l [ ] O(1) O ( 1 ) 回答每个询问。

代码如下:

#include <cstdio>
#include <cctype>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <algorithm>
#define R register
#define IN inline
#define W while
#define gc getchar()
#define ll long long
#define MOD 1000000007
#define MX 1000050
template <class T>
IN void in(T &x)
{
    x = 0; R char c = gc;
    W (!isdigit(c)) c = gc;
    W (isdigit(c))
    x = (x << 1) + (x << 3) + c - 48, c = gc;
}
int T, all, cot;
ll fac[MX], val[MX], inv[MX], finv[MX], ans;
void pre()
{   
    fac[1] = inv[1] = finv[1] = finv[0] = 1;
    for (R int i = 2; i <= 1000000; ++i) fac[i] = i * fac[i - 1] % MOD;
    for (R int i = 2; i <= 1000000; ++i) inv[i] = -(MOD / i) * inv[MOD % i], inv[i] = (inv[i] % MOD + MOD) % MOD;
    for (R int i = 2; i <= 1000000; ++i) finv[i] = finv[i - 1] * inv[i] % MOD;
    val[1] = 0; val[2] = val[0] = 1;
    for (R int i = 3; i <= 1000000; ++i) val[i] = (i - 1) * ((val[i - 1] + val[i - 2]) % MOD) % MOD;
}
IN ll C()
{
    ll ret = fac[all] * finv[cot] % MOD;
    ret = ret * finv[all - cot] % MOD;
    return ret;
}
int main(void)
{
    in(T); pre();
    W (T--)
    {
        in(all), in(cot);
        ans = C() * val[all - cot] % MOD;
        printf("%lld\n", ans);
    }
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值