TonyYin's Blog

Back

P4071 - SDOI2016 - D2T1 - 排列计数#

题目信息#

难度:提高+/省选-

算法:数学递推,逆元

题目描述#

求有多少种 11nn 的排列 aa,满足序列恰好有 mm 个位置 ii,使得 ai=ia_i=i.

答案对 109+710^9+7 取模,多组询问,询问次数为 TT.

测试点编号T=T=n,mn, m\leq测试点编号T=T=n,mn, m\leq
131 \sim 310310^388101210 \sim 1210310^310310^3
464 \sim 610310^31212131413 \sim 145×1055\times 10^510310^3
797 \sim 910310^3100100152015 \sim 205×1055\times 10^510610^6

对于 100%100\% 的数据,0m1060\leq m\leq 10^6.

Subtask\rm{Subtask} 1\rm{1}#

T=103T=10^3n,m8n, m\leq 8.

对于每次询问,暴力枚举所有排列,时间复杂度 O(T×n!)\mathcal{O}(T\times n!),期望得分 15pts\rm{15pts}.

Subtask\rm{Subtask} 2\rm{2}#

想到错排之后,正解非常自然。

根据小学排列组合知识,我们从数列中任意取 nn 个数,让它们满足 ai=ia_i=i,情况数为 CnmC_{n}^{m}.

又因为 nn 个数的排列中,有 mm 个位置满足 ai=ia_i=i,对于剩下的 nmn-m 个位置需要满足 aiia_i\neq i.

这样的情况数是 nmn-m 的错排数,也就是 DnmD_{n-m}.

由于乘法原理,最终的答案就是 Cnm×DnmC_{n}^m\times D_{n-m}.

对于多组询问,我们需要 O(n)\mathcal{O}(n) 预处理组合数和错排数。

错排#

概念#

对于一个 nn 个元素的排列,若一个排列中所有的元素都不在自己的位置上,这样的一个排列称为一个错排。

一般将 nn 个元素的错排数量记作 DnD_n.

通过递推,可以在 O(n)\mathcal{O}(n) 的复杂度内处理出 D1,D2,DnD_1,D_2\cdots, D_n.

递推式#

推导过程不唯一。

第一步,在 [1,n1][1, n-1] 中任取一个元素 ii,将 nn 连向 ii,方法数量为 (n1)(n-1).

第二步,确定 ii 连向哪个元素,分类讨论:

  1. ii 连向 nn.

    此时剩下的 n2n-2 个元素要满足错排,对答案贡献为 (n1)×Dn2(n-1)\times D_{n-2}.

  1. ii 不连向 nn.

    此时,相当于有 n1n-1 个元素需要满足错排,对答案贡献为 (n1)×Dn1(n-1)\times D_{n-1}.

    根据加法原理,得到递推式:

Dn=(n1)×(Dn1+Dn2)\begin{align} D_{n}=(n-1)\times(D_{n-1}+D_{n-2})\tag{n>2} \end{align}

​ 特殊地,D1=0D_1=0D2=1D_2=1.

求逆元 & 组合数#

扩展欧几里得#

概述#

扩展欧几里得算法是用来在已知 a,ba,b 的情况下求解一组 x,yx,y,使满足 ax+by=gcd(a,b)=dax+by=gcd(a,\,b)=d.

gcd(a,b)\gcd(a, b)a,ba, b 的最大公因数,方程一定有整数解

特解#

要计算 gcd(a,b)\gcd(a,\,b),并求出满足条件的 x,yx,y.

将下一个状态 bb(a%b)(a\%b) 表示为:bx1+(a%b)y1=db\cdot x_1+(a\%b)\cdot y_1=d

则:

bx1+(a%b)y1=d,a%b=aab×b\begin{align} \because b\cdot x_1+(a\%b)\cdot y_1=d,\\ \text{又}\because a\%b=a-\left\lfloor\frac{a}{b}\right\rfloor\times b\\ \end{align} gcd(a,b)=d=bx1+(aab×b)y1=bx1+ay1ab×b×y1=a×y1+b×(x1ab×y1)\begin{aligned}\therefore gcd(a,\,b)=d&=b\cdot x_1+(a-\left\lfloor\frac{a}{b}\right\rfloor\times b)\cdot y_1\\ &=b\cdot x_1+a\cdot y_1-\left\lfloor\frac{a}{b}\right\rfloor\times b\times y_1\\ &=a\times y1+b\times(x_1-\left\lfloor\frac{a}{b}\right\rfloor\times y_1) \end{aligned}

所以满足条件的 x=y1,y=x1ab×y1x=y_1,\,y=x_1-\left\lfloor\frac{a}{b}\right\rfloor\times y_1.

int exgcd(int a, int b, int &x, int &y) {//函数返回值为gcd(a,b)
    if(b == 0) {
        x = 1;//gcd(a,b) = a = 1a+0b
        y = 0;
        return a;
    }
    int r = exgcd(b, a % b, x, y);//先递归
    int t = x;//再计算x,y
    x = y;
    y = t - a / b * y;
    return r;
}
cpp

时间复杂度为:O(min(loga,logb))\mathcal{O}(\min(\log a, \log b)),即 O(logn)\mathcal{O}(\log n) 级别。

通解#

假设特解为 (x0,y0)(x_0,y_0),满足 ax0+by0=gcd(a,b)=dax_0+by_0=gcd(a,\,b)=d

则有:

a(x0+kbd)+b(y0kad)=d(k为整数)a(x_0+k\frac{b}{d})+b(y_0-k\frac{a}{d})=d\tag{k为整数}

所以方程的通解为:

x=x0+kbdy=y0kadx=x_0+k\frac{b}{d}\\y=y_0-k\frac{a}{d}

对于其它二元一次不定方程ax+by=cax+by=ccc为任意正整数)只有当 dcd\mid c 时才有整数解,通解形式:

x=cdx0+kbdy=cdy0kadx=\frac{c}{d}x_0+k\frac{b}{d}\\y=\frac{c}{d}y_0-k\frac{a}{d}

同余方程(模方程)#

概述#

指形如 axb(modc)ax\equiv b\pmod c 的一种方程。其中 a,b,ca,b,c 是参数,a,ca,c 是正整数,bb 是小于 cc 的非负整数,x x 是未知数。

解同余方程#

根据同余定义,axb(modc)ax\equiv b\pmod c 可化为 xa+kc=bxa+kc=b. 其中 a,b,ca,b,c 已知,x,kx,k 未知,所以能用扩展欧几里得解同余方程

通解#

如果 bgcd(a,c)b\nmid gcd(a,c),方程无整数解.

否则同扩展欧几里得酸法,求得通解:

x=bdx0+kcdk=bdp0kadx=\frac{b}{d}x_0+k\frac{c}{d}\\k=\frac{b}{d}p_0-k\frac{a}{d}

其中 kZk\in \Bbb{Z}.

最小非负整数解#

因为 x=bdx0+kcdx=\frac{b}{d}x_0+k\frac{c}{d},其中 x0x_0 是方程 xa+kc=gcd(a,c)xa+kc=gcd(a,\,c) 的特解, bdx0\frac{b}{d}x_0 是原同余方程的特解,

x>0x>0,则有 xbdx0(modcd)x\equiv \frac{b}{d}x_0 \pmod{ \frac{c}{d}} 。最小非负整数解为 bdx0%cd\frac{b}{d}x_0\,\%\,\frac{c}{d}.

x<0x<0,同理,最小非负整数解为 (bdx0modcd+cd)modcd(\frac{b}{d}x_0\bmod \frac{c}{d}+\frac{c}{d} )\bmod\frac{c}{d}.

综上,最小非负整数解为:

x=(bdx0modcd+cd)modcdx=(\frac{b}{d}x_0\bmod\frac{c}{d}+\frac{c}{d})\bmod \frac{c}{d}

乘法逆元#

概述#

给定两个整数 aapp,假设存在 xx 使得 ax1(modp))ax\equiv 1\pmod p),称 xxaa 关于 pp乘法逆元

aa 关于 pp 的逆元存在的充分必要条件gcd(a,p)=1\gcd(a,p)=1.

在模意义下,乘一个数 xx 的逆元,可以理解为除以 xx.

求一个数的逆元#

将上面式子变形,变成 ax+kp=1ax+kp=1,用扩展欧几里得能够求出一个 xx.

如果 pp 是质数,有另一种方法求 aa 关于 pp 的逆元(费马小定理)

ap1modp=(ap2×a)modp=(x×a)modp=1a^{p-1}\bmod p=(a^{p-2}\times a)\bmod p=(x\times a)\bmod p=1

所以 aa 关于 pp 的逆元是 ab2modba^{b-2}\bmod b 的值,用快速幂优化。

时间复杂度:均为 O(logn)\mathcal{O}(\log n) 级别。

线性预处理逆元#

O(n)\mathcal{O}(n) 的时间复杂度内,求出任意 nn 个数的逆元,一般用于处理 1n1\sim n 的逆元。

设我们要处理出 a1,a2,ana_1, a_2\cdots, a_n 的逆元。

第一步,预处理出前缀积数组 prei=j=1iai\operatorname{pre}_i=\prod_{j=1}^ia_i.

第二步,求出 pren\operatorname{pre}_n 的逆元 pre_invn\operatorname{pre\_inv}_n,复杂度 O(logn)\mathcal{O}(\log n).

第三步,通过 pre_invi\operatorname{pre\_inv}_i,求出 pre_invi1\operatorname{pre\_inv}_{i-1}.

在模意义下,pre_invi=1a1×a2××ai\operatorname{pre\_inv}_i=\frac{1}{a_1\times a_{2}\times \cdots \times a_{i}}pre_invi1=1a1×a2××ai1=pre_invi×ai\operatorname{pre\_inv}_{i-1}=\frac{1}{a_1\times a_2\times\cdots\times a_{i-1}}=\operatorname{pre\_inv}_{i}\times a_i.

第四步,通过 pre_invi\operatorname{pre\_inv}_i,求出 invi\operatorname{inv}_i.

在模意义下,invi=prei1×pre_invi\operatorname{inv}_i=\operatorname{pre}_{i-1}\times \operatorname{pre\_inv}_{i}.

在上面的步骤中,pre_invi\operatorname{pre\_inv}_i 即为 ii 的阶乘的逆元,在处理组合数时常用。

有另一种方法也可以处理逆元,时间复杂度相同,但不好记,而且证明麻烦,不需要掌握。

inv[1] = 1;
for (int i = 2; i <= n; ++i) {
    inv[i] = (p - p / i) * inv[p % i] % p;
}
cpp

预处理组合数#

预处理后,O(1)\mathcal{O}(1) 地求解 [1,n][1, n] 范围内的组合数,对大质数取模的结果。

facn=n!\operatorname{fac}_n=n!,根据组合数的定义式:

Cnm=n!m!(nm)!=facnfacm×facnmC_n^m=\frac{n!}{m!(n-m)!}=\frac{\operatorname{fac}_n}{\operatorname{fac}_m\times \operatorname{fac}_{n-m}}

可以得到:

Cnmmodp=facn(facm)1(facnm)1C_n^m \bmod p = \operatorname{fac}_n (\operatorname{fac}_m)^{-1}(\operatorname{fac}_{n-m})^{-1}

所以预处理出 [1,n][1, n] 的阶乘,以及阶乘的逆元,每次即可 O(1)\mathcal{O}(1) 求组合数。

int fac[], inv_fac[];
fac[0] = 1;
for(int i = 1; i <= n; i++)
	fac[i] = fac[i - 1] * i % p;
inv_fac[n] = power(fac[n], p - 1);
for(int i = n - 1; i >= 0; i--)
	inv_fac[i] = inv_fac[i + 1] * (i + 1) % p;
cpp

P4343 - SHOI2015 - 自动刷题机#

题目信息#

难度:提高+/省选-

算法:二分答案

题目描述#

自动刷题机刷题的方式非常简单:首先会瞬间得出题目的正确做法,然后开始写程序。每秒,自动刷题机的代码生成模块会有两种可能的结果:

  1. 写了 xx 行代码
  2. 心情不好,删掉了之前写的 yy 行代码。(如果 yy 大于当前代码长度则相当于全部删除。)

对于一个 OJ,存在某个固定的正整数长度 nn,一旦自动刷题机在某秒结束时积累了大于等于 nn 行的代码,它就会自动提交并 AC 此题,然后新建一个文件(即弃置之前的所有代码)并开始写下一题。SHTSC 在某个 OJ 上跑了一天的自动刷题机,得到了很多条关于写代码的日志信息。他突然发现自己没有记录这个 OJ 的 nn 究竟是多少。所幸他通过自己在 OJ 上的 Rank 知道了自动刷题机一共切了 kk 道题,希望你计算 nn 可能的最小值和最大值。

题目给定 ll 条刷题日志,每条有一个整数 xix_i 表示添加的代码行数。

1l105,109xi1091\leq l\leq 10^5, -10^9\leq x_i\leq 10^9.

2021年五一假期 省选真题分享
https://www.tonyyin.top/blog/oi-solution/pre-20210504
Author TonyYin
Published at May 4, 2021
Comment seems to stuck. Try to refresh?✨