Files
python/TangDou/AcWing_TiGao/T5/ZhuHeJiShu/1312.md
HuangHai 1f397eca87 'commit'
2025-08-30 18:35:01 +08:00

207 lines
7.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

##[$AcWing$ $1312$. 序列统计](https://www.acwing.com/problem/content/description/1314/)
### 一、题目描述
给定三个整数 $N,L,R$,统计长度在 $1$ 到 $N$ 之间,元素大小都在 $L$ 到 $R$ 之间的 **单调不降序列** 的数量。
输出答案对 $10^6+3$ 取模的结果。
**输入格式**
输入第一行包含一个整数 $T$,表示数据组数。
第二到第 $T+1$ 行每行包含三个整数 $N,L,R$。
**输出格式**
输出包含 $T$ 行,每行有一个数字,表示你所求出的答案对 $10^6+3$ 取模的结果。
**数据范围**
$0≤N,L,R≤10^9,1≤T≤100$,输入数据保证 $L≤R$。
**输入样例**:
```cpp {.line-numbers}
2
1 4 5
2 4 5
```
输出样例:
```cpp {.line-numbers}
2
5
```
**样例解释**
对于第一组输入,满足条件的两个序列为 $\{4\},\{5\}$。
对于第二组输入,满足条件的五个序列为$\{4\},\{5\},\{4,4\},\{5,5\},\{4,5\}$
### 二、算法分析
#### 1、隔板法
很多数论问题我们一般都是要枚举某一个量的,要不然那都是未知量(当然也有单纯推公式的数论,不过大部分的还是要枚举一些量)
数论枚举的量一般是那种未知且不能用公式表示的量
这题我们要枚举的是 **序列长度**
既然我们枚举了长度,那么此刻对于我们来说长度是已知的量了,设长度$=k$
每个元素的大小都要在$[L\sim R]$之间,我们发现我们关心的不是元素具体的值而是元素的**相对值**,所以我们可以采用 **类似离散化** 的思路,把元素映射到 $[0 \sim R-L]$。
此刻可以列出等式 $$\large 0 \leq a_1\leq a_2\leq a_3\leq a_4...\leq a_k<=R-L$$
我们要求满足这样条件的一组解显然直接写很麻烦,数论问题在遇到麻烦的时候最好的办法就是构造一个等价的式子化繁为简。
面对一组数我们要么 **构建前缀和**, 要么 **构建差分**。
本题构建一个差分数组$x_1 \sim x_k$,可以得到一个等价等式
$$ \large x_1+x_2+...+x_k=a_k $$
$\because 0 \leq a_k \leq R-L \\
\therefore 0<=x_1+x_2+..x_k<=R-L$
> **差分数组举栗子**
> 原数组 :$[3, 1, 4, 1, 5, 9, 2]$
> 差分数组:$[3, -2, 3, -3, 4, 4, -7]$
因为 **差分数组与原数组是一一对应**的,所以我们只要求上述满足条件的 ${x_1}...{x_k}$ 数列的个数,问题转化为:
> **用 <font color='red' size=4><b>不超过</b></font> $r−l$ 个小球放入 $k$ 个盒子,盒子允许为空的方案数**
看到盒子允许为空,马上联想到 **隔板法** 中的第三种情况:**少分型**,也就是先借后还那个。
但那个是 <font color='red' size=4><b>等于</b></font> $r-l$个小球,现在是 <font color='blue' size=4><b> 不超过</b></font>!就是一个是等号,一个是小于等于号,不一样,肯定还会有变化!
我们先来思考等于的,也就是原始的隔板法少分型是如何处理的:
我们先借来$k$个小球,问题变成 $r−l+k$ 个小球放入 $k$ 个盒子,且盒子不空的方案数,此时只需考虑小球间的间隙,最后再拿走这$k$个小球即可。
⚪···⚪|⚪···⚪|···|⚪···⚪|⚪···⚪
如图所示,只在小球之间的间隙放隔板,两边不能放隔板,放完$k-1$隔板划分为$k$个盒子,然后每个盒子中再都取走$1$个小球,总结起来公式就是:
$$\large C_{r-l+k-1}^{k-1}$$
**$Q$:现在是小于等于,不是等于,有什么变化呢?**
小于等于,就是说小球不用用完,可以剩下几个不放进盒子。
不放的话理解为扔掉了。
⚪···⚪|⚪···⚪|···|⚪···⚪|后续都是扔掉的球
扔掉怎么在上面的划分中表现呢?假设我们都从最右边扔掉,最后一个隔板的使命就是找到哪些需要扔掉,定义它右侧的小球都是需要扔掉的。
- 如果最后一个隔板是在最后,右面一个小球都没有,表示没有小球被扔掉。
**这意味着不光小球间的间隙可以放隔板,最后一个位置也可以放隔板!**
- 如果它右面有一个小球,就表示这个小球被扔掉了。
- 如果它右面有两个小球,就表示这两个小球被扔掉了。
- ....
在扔掉若干个小球之后,剩下的部分,就是留下来参加放入盒子的小球。
**总结**
- 最后一个隔板用来截断、丢弃多余的小球。
- 其它隔板用来划分成盒子。
这样一来,就需要<font color='blue' size=4><b> 多放一个隔板</b></font>!
接下来我们就可以运行排列组合的知识,共 $n$ 个球,有 $n−1$ 个缝隙,还需要加上最右边的一个 **缝隙** ,共 $n$ 个缝隙,插入 $k$ 个隔板。答案为 :$\large \displaystyle \sum_{k=1}^{n}C_{r-l+k}^k$
#### 2、计算
数据范围 $10^9$,显然不能枚举长度 $k$ 来累加答案,需要进行数学推导:
由于序列长度是 $k$ 的方案是 $\displaystyle \sum_{k=1}^nC_{r−l+k}^k$,令 $r−l=m$,则 $k$ 取 $1,2....N$ 时的 <font color='red'><b>总方案数</b></font> 是
$$\large \displaystyle \sum_{k=1}^nC_{m+k}^k =C_{m+1}^1+C_{m+2}^2+C_{m+3}^3+...+C_{m+n}^n$$
下面是一个 <font color='red'><b>经典的变形技巧</b></font> ,利用公式:
$$\large \displaystyle C_{a}^b=C_{a-1}^{b}+C_{a-1}^{b-1} \\
\Rightarrow \\
C_{m+1}^{0}+C_{m+1}^{1}=C_{m+2}^{1}$$
然后形成多米诺骨牌,顺次形成递推关系,一路向前~
$\large =C_{m+1}^0 +C_{m+1}^1+C_{m+2}^2+C_{m+3}^3+...+C_{m+n}^n - C_{m+1}^0 \\
=C_{m+2}^1+C_{m+2}^2+C_{m+3}^3+...+C_{m+n}^n - C_{m+1}^0 \\
... \\
=C_{m+n+1}^n-C_{m+1}^0 \\
=C_{m+n+1}^n-1
$
题目给定 $P = 10^6+3, N = 10^9$,用 **[卢卡斯定理](https://www.cnblogs.com/littlehb/p/15010406.html)** 解决
时间复杂度 $O(Plog_pN)$
$Lucas$定理的时间复杂度是$O(Plog_pN)$
### 三、实现代码
```cpp {.line-numbers}
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int p = 1000003;
/**
* 功能:快速幂模板
* @param a
* @param k
* @param p
* @return
*/
int qmi(int a, int k, int p) {
int res = 1;
while (k) {
if (k & 1) res = (LL)res * a % p;
a = (LL)a * a % p;
k >>= 1;
}
return res;
}
/**
* 功能:组合数模板
* @param a 在a个数中
* @param b 取b个数
* @param p 一个质数,用来取模
* @return 多少种办法
*/
int C(int a, int b, int p) {
if (a < b) return 0;
int down = 1, up = 1;
for (int i = a, j = 1; j <= b; i--, j++) {
up = (LL)up * i % p;
down = (LL)down * j % p;
}
return (LL)up * qmi(down, p - 2, p) % p;
}
/**
* 功能:Lucas公式模板
* @param a
* @param b
* @param p
* @return
*/
int lucas(LL a, LL b, int p) {
if (a < p && b < p) return C(a, b, p);
return (LL)C(a % p, b % p, p) * lucas(a / p, b / p, p) % p; //套用公式,还有个递归
}
int main() {
int T;
cin >> T;
while (T--) {
int n, l, r;
cin >> n >> l >> r;
cout << (lucas(r - l + n + 1, r - l + 1, p) - 1 + p) % p << endl;
}
return 0;
}
```