This commit is contained in:
HuangHai
2025-08-30 18:35:01 +08:00
commit 1f397eca87
6258 changed files with 1487241 additions and 0 deletions

View File

@@ -0,0 +1,18 @@
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define endl "\n"
int n, k, l, r;
int ans;
signed main() {
cin >> n >> k;
ans = n * k; // 看题解的推导公式
for (l = 1; l <= n; l = r + 1) { // 枚举左端点,每次跳着走,下次的位置就是本次r的位置+1
if (k / l == 0) break; // 1、当k/l=0的时候,比如3/4=0,3/5=0,3/6=0...,以后就都是0,不用再往后算了,无贡献
r = min(k / (k / l), n); // 2、注意右端点和n取个min,>n没有贡献
ans -= (k / l) * (l + r) * (r - l + 1) / 2;
// 等差数列求和:首项:l,末项:r.项数:(r-l+1),根据公式推导,k/l = 块内值
}
cout << ans << endl;
}

View File

@@ -0,0 +1,134 @@
##[$AcWing$ $199$. 余数之和](https://www.acwing.com/problem/content/description/201/)
### 一、题目描述
给出正整数 $n$ 和 $k$,计算 $j(n,k)=k~mod~1+k~mod~2+k~mod~3+…+k~mod~n$ 的值。
例如 $j(5,3)=3~mod~1+3~mod~2+3~mod~3+3~mod~4+3~mod~5=0+1+0+3+3=7$。
**输入格式**
输入仅一行,包含两个整数 $n,k$。
**输出格式**
输出仅一行,即 $j(n,k)$。
**数据范围**
$1≤n,k≤10^9$
**输入样例:**
```cpp {.line-numbers}
5 3
```
**输出样例:**
```cpp {.line-numbers}
7
```
### 二、暴力作法
> **注**:暴力作法用于骗分+理解题意
```cpp {.line-numbers}
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define endl "\n"
int n, k, ans;
signed main() {
// 通过了 6/10个数据
cin >> n >> k;
for (int i = 1; i <= n; i++) ans += k % i;
cout << ans << endl;
}
```
### 三、整除分块
我们先研究一下如下的序列有哪些特点,再结合本题实际给出解题思路:
$$\large \displaystyle f(i)=\lfloor \frac{k}{i} \rfloor ~ (i \in [1,n])$$
> **注**:本题要求的是$[1 \sim n]$之内所有数字对数字$k$ **取模**,我们不直接研究取模,而是在研究$[1 \sim n]$之内每个数字被$k$整除的结果。这个结果,与 **取模** 是有关系的,后面的题解中也会证明这一点。
举个例子,$n = 4,k=5$,
$f(1)=k/1=5/1=5 \\
f(2)=k/2=5/2=2 \\
f(3)=k/3=5/3=1,f(4)=k/4=5/4=1,f(5)=k/5=5/5=1$
一字排开,得到$5,2,1,1,1$,相同的分到一个 **连续一样的数字段** 中,直观一点,写成:$[5],[2],[1,1,1]$
<center><img src='https://cdn.acwing.com/media/article/image/2022/03/25/94631_bd6771ecab-4ocKnIkdlrW9JMs.png'></center>
### 四、本题题解
**题意**:给出$n,k$,求 $\displaystyle \sum_{i=1}^{n}k \ mod \ i$。
首先取模形式不好处理,根据取模运算定义做 **等价变形**:
$$\large \sum_{i=1}^nk \ mod \ i=\sum_{i=1}^n(k-⌊\frac{k}{i}⌋\cdot i)$$
化简
$$\large n \cdot k-\sum_{i=1}^n ⌊\frac{k}{i}⌋\cdot i$$
我们发现重点在于求$\large \displaystyle \sum_{i=1}^n ⌊\frac{k}{i}⌋ \cdot i$。
发现$\large \underset{\text{i=1}}{\overset{\text{n}}{⌊\frac{k}{i}⌋}}$ 的值 **呈块状分布**(即结果数组分成若干块,每块中值相等),是 **整除分块**。
#### 1、 块内的累加和是多少?
首先 **一个块内部** 的答案显然是好求的,设块起点为 $l$,终点为 $r$,同时$\displaystyle \large ⌊\frac{k}{l}⌋=⌊\frac{k}{l+1}⌋=...=⌊\frac{k}{r}⌋$,
累加和是$\large \displaystyle ⌊\frac{k}{l}⌋ \cdot \sum_{i=l}^r i$。
#### 2、如何根据一个块的起点求块的终点呢?
① 下一个块的起点就是前一个块的终点加$1$。
② 第一个块的起点位置坐标肯定是$1$
> **注**:这里说的第一个块的起点,是指最终结果数组中块的下标位置,如上例,就是
> 序列 ` 5 2 1 1 1`
> **序号** ` 1 2 3 4 5`
③ 如果有办法能通过一个块的起点,找到这个块的终点,那就能确定一个块的范围,我们来研究一下这个办法:
首先由于 **块内值** 都相同,可以设 $\large \displaystyle x= ⌊\frac{k}{l}⌋= ⌊\frac{k}{r}⌋$
根据 $\displaystyle \large x=⌊\frac{k}{r}⌋$ ① $\Rightarrow$ 变形 $\Rightarrow $ $\large \displaystyle x \cdot r \leq k$ $\Rightarrow$ 变形 $\Rightarrow$ $\large \displaystyle r \leq ⌊\frac{k}{x}⌋$ ②
将$\large \displaystyle x=⌊\frac{k}{l}⌋$ ① 代入 ②,得$\large \displaystyle r \leq ⌊\frac{k}{⌊\frac{k}{l}⌋}⌋$,这样就确定了$r$的最大值。
> **注**:当$k,l$确定时,$r$的上限就已经确定,同时,由于第一起点块的$l=1$,我们就可以一路向后递推找出所有块的右边界!一旦有了右边界,就可以利用 $\large \displaystyle ⌊\frac{k}{l}⌋\cdot \sum_{i=l}^ri$来快速计算出区间和。
#### 3、时间复杂度【选读】
当 $i>\sqrt{n}$ 时,$\large \displaystyle ⌊\frac{k}{i}⌋ \leq \sqrt{n}$,也就是说原式只有小于 $\sqrt{k}$ 种取值。
> **注**:纯粹的数学思考,除以一个$ \geq \sqrt{n}$的值,$n,k$是同一个数据级别$\leq 10^9$,结果值当然是$\leq \sqrt{n}$
当$i \leq \sqrt{n}$时,$i$只有小于$\sqrt{k}$种取值,也就是说原式也只有小于$\sqrt{k}$种取值。
所以最多有 $2\sqrt{n}$ 个块,我们对于每个块可以 $O(1)$ 计算,时间可以通过。
### $Code$
```cpp {.line-numbers}
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define endl "\n"
int n, k, l, r;
int ans;
signed main() {
cin >> n >> k;
ans = n * k; // 看题解的推导公式
for (l = 1; l <= n; l = r + 1) { // 枚举左端点,每次跳着走,下次的位置就是本次r的位置+1
if (k / l == 0) break; // 1、当k/l=0的时候,比如3/4=0,3/5=0,3/6=0...,以后就都是0,不用再往后算了,无贡献
r = min(k / (k / l), n); // 2、注意右端点和n取个min,>n没有贡献
ans -= (k / l) * (l + r) * (r - l + 1) / 2;
// 等差数列求和:首项:l,末项:r.项数:(r-l+1),根据公式推导,k/l = 块内值
}
cout << ans << endl;
}
```

View File

@@ -0,0 +1,12 @@
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define endl "\n"
int n, k, ans;
signed main() {
// 通过了 6/10个数据
cin >> n >> k;
for (int i = 1; i <= n; i++) ans += k % i;
cout << ans << endl;
}

Binary file not shown.

View File

@@ -0,0 +1,261 @@
##[$AcWing$ $214$. $Devu$和鲜花 ](https://www.acwing.com/problem/content/description/216/)
### 一、题目描述
$Devu$ 有 $N$ 个盒子,第 $i$ 个盒子中有 $A_i$ 枝花。
同一个盒子内的花颜色相同,不同盒子内的花颜色不同。
$Devu$ 要从这些盒子中选出 $M$ 枝花组成一束,**求共有多少种方案**。
**若两束花每种颜色的花的数量都相同,则认为这两束花是相同的方案。**
结果需对 $10^9+7$ 取模之后方可输出。
**输入格式**
第一行包含两个整数 $N$ 和 $M$。
第二行包含 $N$ 个空格隔开的整数,表示 $A_1,A_2,…,A_N$。
**输出格式**
输出一个整数,表示方案数量对 $10^9+7$ 取模后的结果。
**数据范围**
$1≤N≤20,0≤M≤10^{14},0≤A_i≤10^{12}$
**输入样例:**
```cpp {.line-numbers}
3 5
1 3 2
```
**输出样例:**
```cpp {.line-numbers}
3
```
### 二、隔板法复习
#### 1、隔板基本法【每组小球数至少为$1$】
$Q$:有 `m` 个相同的小球,要求分到 `n` 个盒子里,<font color='red'><b>每组至少小球数为$1$</b></font>,问有多少种不同的分法?
$A$:从`m-1`个空隙中找出`n-1`个位置放上隔板,就可以保证最终分成`n`组,并且,每一组的数量都最少是`1`个或以上。
<center><img src='https://img-blog.csdnimg.cn/20190423123438869.png'></center>
其实,有多少种划分方法,就是有多少种不同颜色组合的方案,比如:
<center><img src='https://img-blog.csdnimg.cn/20190423123537228.png'></center>
如上图,把隔板隔开的不同小球,涂上不同颜色,不就是不同颜色的组合方案了嘛~
**隔板法的思路**:找空隙(**共$m-1$个空隙**),插入隔板(**$n-1$个隔板,这样才能分成$n$组**),这类问题比较直观:$$\LARGE C_{m-1}^{n-1} $$
#### 2、隔板扩展法【每组小球数可以为$0$】
有时,有的问题不能直接使用隔板法,比如:
有 `m` 个相同的元素,要求分到 `n` 组中,<font color='red'><b>每组个数可以为$0$</b></font>,问有多少种不同的分法?
这个问题对比上面的 **隔板法**,区别在于没有强调 **每组至少元素数为$1$** ,每组是可以为`0`的,这样一来,无法直接使用隔板法。
采用一个 **变形**,就可以使用隔板法:
原来有`m`个小球,我再多拿`n`个小球,就是一共`m+n`个小球。这些小球,每组先来一个,就保证了每组 **至少** 小球数为$1$,就 **转化为隔板法的基本问题** 了。
现在是`n+m`个小球,空隙是 `n+m-1`个。
![](https://dsideal.obs.cn-north-1.myhuaweicloud.com/HuangHai/BlogImages/202312060828700.png)
由于还是要分成`n`组,所以现在需要在`n+m-1`个空隙中找到`n-1`个位置放入隔板,分法就是$\large \displaystyle C_{m+n-1}^{n-1}$,最后,把每个分组中再取走增加进去的那个小球,这样就和原问题一致了。
### 三、本题思路
**[容斥原理模板题 $AcWing$ $890$. 能被整除的数 ](https://www.cnblogs.com/littlehb/p/15389237.html)**
#### 1、转化为 隔板扩展法
先不考虑每个盒子的个数$A_i$限制,**简化问题** 为:从$n$个盒子中每个都任意选出$x_i$​个,每个$x_i$**可以为$0$**,可以使用 **隔板扩展法**,答案:
$$\large \displaystyle C_{m + n − 1}^{n-1}$$
> **解读**:盒子数量 $n$,需要$n-1$个隔板。但要求每个盒子中小球数量可以为$0$,所以,先借$n$个小球,最后归还即可。此时,共$m+n$个小球,有 $m+n-1$个空隙,因为需要划分为$n$个盒子,所以,需要找出$n-1$个空隙,即$C_{m+n-1}^{n-1}$
#### 2、每组个数限制$a_i$
但题目并没有那么简单,每个箱子中选择的个数$x_i$,不是随意多少都行的,需要小于 **现存个数** $a_i$。
<font color='red'><b>这个限制不好加上去~</b></font> ,正着想困难,我们倒着想试试:
<font color='blue' size=4><b>问题转化为补集:所有方案数-不满足至少一种条件的方案数</b></font>
**(1)、如果我们把不满足条件的去掉,是不是就行了呢?**
什么是不满足条件呢?答:如果某个$x_i>a_i$,就是不满足! 即$x_i>=a_i+1$
<font color='red'><b>令$S_i$代表第$i$组不满足的方案个数,即在第$i$组中至少取走$a_i+1$个的方案个数。</b></font>
**(2)、把这样不满足的都减掉是不是就是答案呢?**
**答**:<font color='red'><b>不是!</b></font>
比如要求第$1$组中不能多于$2$个,第$2$组中不能多于$3$个,现在有一组答案$(3,4,2,..)$,表示第一组选择了$3$个,第二组中选择了$4$个,很明显这个答案对于两个限制都是不能满足的:
- ① 在检查到第一组时,此答案被去掉一次
- ② 在检查到第二组时,它又被去掉了一次
但问题是这只是一组数据,现在被去了两次啊!噢,这是 <font color='red'><b>容斥原理</b></font> 的问题啊~,还得把减两次的加回来一次才对!
$$\large C_{m+n-1}^{n-1}-|S_1|-|S_2|-...-|S_n|+|S_1\cap S_2|+|S_1 \cap S_3|+...$$
> **解读**:模板题中奇数的加,偶数的减,这里怎么是奇数的减,偶数的加呢?
> 思考一下知道,这应该是题目本来就是要 **去掉不合法** 的情况,去掉嘛,就是减
> **小结**:受一个条件限制的,需要减去,受两个条件限制的由于已经被单个条件减去了两次,所以需要加上两者的交集,以此类推,就是 **奇数次出现的减,偶数次出现的加**。
> **总结**:容斥原理可不是一定要奇数加,偶数减,需要具体问题具体分析。究其原因,就是因为前面可能有负号,脱括号后后面的符号就会反转!
**(3)、$S_i$怎么求?**
比如求$S_1$,代表从第一组里 <font color='red' size=4><b>至少取出$A_1+1$朵花</b></font>,此时还剩$m−(A_1+1)$朵花。
则问题转化为选$m−(A_1+1)$只花分$n$组的问题。
> **解读**:
> 至少取出$A_1+1$只花,那就先拿出来放一边。然后考虑剩下的$m-(A_1+1)$只花怎么分的问题。
> 把这些花直接分$n$组,分组数量可以为$0$,划分完后,再把刚才拿走的那些花还给第$1$组就行了。
在$m-(A_1+1)$个小球需要划分$n$组,并且每组个数可以为$0$的方案数:
>$$\large |S_1|=C_{m-(A_1+1)+n-1}^{n-1}$$
**(4)、$|S_1 \cap S_2|$怎么求?**
$|S_1 \cap S_2|$表示从第一组里取出至少$A_1+1$朵花,并且,从第二组里取出至少$A_2+1$朵花。方案数
$$\large |S_1 \cap S_2|=C_{m-(A_1+1)-(A_2+1)+n-1}^{n-1}$$
> **解读**:参考上面的解释内容,先把$A_1+1,A_2+1$拿出来,然后把剩下的花$m-(A_1+1)-(A_2+1)$只花继续划分为$n$组,每组允许为$0$只花,**隔板扩展法**,最后再把$A_1+1,A_2+1$放到第$1,2$组中去。
**(5)、计算公式**
整理一下,得到:
$$\large res=C_{m+n-1}^{n-1}-\sum_{i=1}^{n}C_{m+n-1-(A_i+1)}^{n-1}+\sum_{i<j}^{n}C_{m+n-1-(A_i+1)-(A_j+1)}^ {n-1}-...$$
**(6)、为什么可以用费马小定理求逆元?**
$Q$:$(n−1)!$一定不是质数$p$的倍数吗?为什么呢?
**答**:$n$最大是$20$,$p$是$1e9 + 7$,考虑$n$是从$1$到$20$的乘积,每一个乘数都$p$互质,所以$(n - 1)!$一定与$p$互质,所以一定不是倍数。
**(7)、代码实现**
从$1$枚举到$2^{n-1}$。然后把 **每一个限制条件** 看成一个二进制位,如果是$1$代表 **遵守** ,$0$代表 **不遵守** 这个条件,奇数个就减,偶数个就加。
> **解读**:
> $0000$代表所有限制条件都不遵守,也就是初始值$C_{n+m-1}^{n-1}$
> $0001$代表第$1$个约束条件遵守,其它约束条件不遵守
> ...
> $1111$代表所有限制条件都遵守
怎么去算组合数,可以发现虽然$M$非常大,但是$N$很小,所以只需按着定义去算,大概是$O(N)$的复杂度。
总的复杂度:$O(2^N∗N)$
#### $Code$
```cpp {.line-numbers}
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define endl "\n"
const int N = 20, mod = 1e9 + 7;
int A[N];
int n, m;
// 快速幂
int qmi(int a, int k) {
int res = 1;
while (k) {
if (k & 1) res = res * a % mod;
a = a * a % mod;
k >>= 1;
}
return res;
}
int C(int a, int b) {
if (a < b) return 0;
int up = 1, down = 1;
for (int i = a; i > a - b; i--) up = i % mod * up % mod;
for (int i = 1; i <= n - 1; i++) down = i * down % mod; //(n-1)! % mod
down = qmi(down, mod - 2); // 费马小定理求逆元
return up * down % mod; // 费马小定理
}
signed main() {
cin >> n >> m;
for (int i = 0; i < n; i++) cin >> A[i];
int res = C(n + m - 1, n - 1);
for (int i = 1; i < (1 << n); i++) {
int sum = 0, cnt = 0;
for (int j = 0; j < n; j++) {
if (i >> j & 1) {
sum += A[j] + 1;
cnt++;
}
}
if (cnt & 1)
res = (res - C(m + n - 1 - sum, n - 1) + mod) % mod;
else
res = (res + C(m + n - 1 - sum, n - 1)) % mod;
}
cout << res << endl;
}
```
---
附上最开始我没有看懂的$yxc$大佬代码,让我们一起批判他吧:
```cpp {.line-numbers}
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define endl "\n"
const int N = 20, mod = 1e9 + 7;
int A[N];
int n, m;
// 快速幂
int qmi(int a, int k) {
int res = 1;
while (k) {
if (k & 1) res = res * a % mod;
a = a * a % mod;
k >>= 1;
}
return res;
}
int C(int a, int b) {
if (a < b) return 0;
int up = 1, down = 1;
for (int i = a; i > a - b; i--) up = i % mod * up % mod;
for (int i = 1; i <= n - 1; i++) down = i * down % mod; //(n-1)! % mod
down = qmi(down, mod - 2); // 费马小定理求逆元
return up * down % mod; // 费马小定理
}
signed main() {
cin >> n >> m;
for (int i = 0; i < n; i++) cin >> A[i]; // 第i个盒子中有A[i]枝花,限制条件
// yxc这里写的代码太随意了,把我直接干蒙圈了!
// 根据推导的式子,这里需要一个全部方案数=C(n + m - 1, n - 1)
// 也就是说 res的初始值就是上面的全部方案数。
// 可是,yxc大佬的大脑与正常人不一样,他居然没有给初始值,直接把初始值也写到下面的容斥原理代码中!!!
// 也就是所有限制条件全部不采用,也就是全部不受限制!也就是全部方案数!!!
int res = 0;
for (int i = 0; i < 1 << n; i++) { // 容斥原理的项数,0000 代表所有限制条件都不遵守,0001代表第1个限制条件遵守,其它3个不遵守
int sum = 0, cnt = 0; // 奇数个限制条件,需要减;偶数个限制条件,需要加。现在这种限制条件组合状态,是奇数个限制,还是偶数个限制?
for (int j = 0; j < n; j++) // 枚举状态的每一位
if (i >> j & 1) { // 如果此位是1
sum += A[j] + 1; // 拼公式
cnt++; // 限制条件个数,奇数个减,偶数个加
}
if (cnt & 1)
res = (res - C(m + n - 1 - sum, n - 1) + mod) % mod;
else
res = (res + C(m + n - 1 - sum, n - 1)) % mod;
}
cout << (res + mod) % mod << endl;
}
```

View File

@@ -0,0 +1,50 @@
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define endl "\n"
const int N = 20, mod = 1e9 + 7;
int A[N];
int n, m;
// 快速幂
int qmi(int a, int k) {
int res = 1;
while (k) {
if (k & 1) res = res * a % mod;
a = a * a % mod;
k >>= 1;
}
return res;
}
int C(int a, int b) {
if (a < b) return 0;
int up = 1, down = 1;
for (int i = a; i > a - b; i--) up = i % mod * up % mod;
for (int i = 1; i <= n - 1; i++) down = i * down % mod; //(n-1)! % mod
down = qmi(down, mod - 2); // 费马小定理求逆元
return up * down % mod; // 费马小定理
}
signed main() {
cin >> n >> m;
for (int i = 0; i < n; i++) cin >> A[i];
int res = C(n + m - 1, n - 1);
for (int i = 1; i < (1 << n); i++) {
int sum = 0, cnt = 0;
for (int j = 0; j < n; j++) {
if (i >> j & 1) {
sum += A[j] + 1;
cnt++;
}
}
if (cnt & 1)
res = (res - C(m + n - 1 - sum, n - 1) + mod) % mod;
else
res = (res + C(m + n - 1 - sum, n - 1)) % mod;
}
cout << res << endl;
}

View File

@@ -0,0 +1,54 @@
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define endl "\n"
const int N = 20, mod = 1e9 + 7;
int A[N];
int n, m;
// 快速幂
int qmi(int a, int k) {
int res = 1;
while (k) {
if (k & 1) res = res * a % mod;
a = a * a % mod;
k >>= 1;
}
return res;
}
int C(int a, int b) {
if (a < b) return 0;
int up = 1, down = 1;
for (int i = a; i > a - b; i--) up = i % mod * up % mod;
for (int i = 1; i <= n - 1; i++) down = i * down % mod; //(n-1)! % mod
down = qmi(down, mod - 2); // 费马小定理求逆元
return up * down % mod; // 费马小定理
}
signed main() {
cin >> n >> m;
for (int i = 0; i < n; i++) cin >> A[i]; // 第i个盒子中有A[i]枝花,限制条件
// yxc这里写的代码太随意了,把我直接干蒙圈了!
// 根据推导的式子,这里需要一个全部方案数=C(n + m - 1, n - 1)
// 也就是说 res的初始值就是上面的全部方案数。
// 可是,yxc大佬的大脑与正常人不一样,他居然没有给初始值,直接把初始值也写到下面的容斥原理代码中!!!
// 也就是所有限制条件全部不采用,也就是全部不受限制!也就是全部方案数!!!
int res = 0;
for (int i = 0; i < 1 << n; i++) { // 容斥原理的项数,0000 代表所有限制条件都不遵守,0001代表第1个限制条件遵守,其它3个不遵守
int sum = 0, cnt = 0; // 奇数个限制条件,需要减;偶数个限制条件,需要加。现在这种限制条件组合状态,是奇数个限制,还是偶数个限制?
for (int j = 0; j < n; j++) // 枚举状态的每一位
if (i >> j & 1) { // 如果此位是1
sum += A[j] + 1; // 拼公式
cnt++; // 限制条件个数,奇数个减,偶数个加
}
if (cnt & 1)
res = (res - C(m + n - 1 - sum, n - 1) + mod) % mod;
else
res = (res + C(m + n - 1 - sum, n - 1)) % mod;
}
cout << (res + mod) % mod << endl;
}

View File

@@ -0,0 +1,59 @@
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define endl "\n"
const int N = 50010;
// 线性筛法求莫比乌斯函数(枚举约数)
int mu[N], sum[N]; // 莫比乌斯函数的前缀和
int primes[N], cnt;
bool st[N];
void get_mobius(int n) {
mu[1] = 1;
for (int i = 2; i <= n; i++) {
if (!st[i]) {
primes[cnt++] = i;
mu[i] = -1;
}
for (int j = 0; primes[j] * i <= n; j++) {
int t = primes[j] * i;
st[t] = true;
if (i % primes[j] == 0) {
mu[t] = 0;
break;
}
mu[t] = -mu[i];
}
}
// 维护莫比乌斯函数前缀和
for (int i = 1; i <= n; i++) sum[i] = sum[i - 1] + mu[i];
}
signed main() {
// 筛法求莫比乌斯函数
get_mobius(N - 1);
int T;
cin >> T;
while (T--) {
int a, b, d;
cin >> a >> b >> d;
// 套路啊,满满的套路,直接先用最大公约数a/gcd(a,b)=a',b/gcd(a,b)=b',映射到a',b'
a /= d, b /= d;
// n为 min(a', b')
int n = min(a, b);
int res = 0;
// l r, 是每一段的左右边界
// 每次只能取较小的那个上界作为这一段的右端点r
// 然后下次迭代时下一段的左端点就是r + 1
for (int l = 1, r; l <= n; l = r + 1) { // 分块大法
r = min(n, min(a / (a / l), b / (b / l)));
res += (sum[r] - sum[l - 1]) * (a / l) * (b / l);
}
cout << res << endl;
}
}

View File

@@ -0,0 +1,173 @@
##[$AcWing$ $215$. 破译密码](https://www.acwing.com/problem/content/description/217/)
### 一、题目描述
达达正在破解一段密码,他需要回答很多类似的问题:
对于给定的整数 $a,b$ 和 $d$,有多少正整数对 $x,y$,满足 $x≤a,y≤b$,并且 $gcd(x,y)=d$。作为达达的同学,达达希望得到你的帮助。
**输入格式**
第一行包含一个正整数 $n$,表示一共有 $n$ 组询问。
接下来 $n$ 行,每行表示一个询问,每行三个正整数,分别为 $a,b,d$。
**输出格式**
对于每组询问,输出一个正整数,表示满足条件的整数对数。
**数据范围**
$1≤n≤50000,1≤d≤a,b≤50000$
**输入样例:**
```cpp {.line-numbers}
2
4 5 2
6 4 3
```
**输出样例:**
```cpp {.line-numbers}
3
2
```
提示:$gcd(x,y)$ 返回 $x,y$ 的最大公约数。
### 二、题目分析
#### 前导知识
**[$1$、容斥原理 $AcWing$ $890$. 能被整除的数 ](https://www.cnblogs.com/littlehb/p/15389237.html)**
> **小结**:
> ① 二进制对质数序列的组合式枚举
> ② 奇数加,偶数减
**[$2$、数论分块 $AcWing$ $199$. 余数之和](https://www.cnblogs.com/littlehb/p/16389420.html)**
> **小结**:
> 给出$n,k$,求 $\displaystyle \sum_{i=1}^n k \ \% \ i$
**核心代码**
```cpp {.line-numbers}
ans = n * k; // 看题解的推导公式
for (l = 1; l <= n; l = r + 1) { // 枚举左端点,每次跳着走,下次的位置就是本次r的位置+1
if (k / l == 0) break; // 1、当k/l=0的时候,显然这段以及后面(有单调性)已经没有贡献了,可以 break。
r = min(k / (k / l), n); // 2、注意右端点和n取个min,因为>n没有贡献了。
ans -= (k / l) * (l + r) * (r - l + 1) / 2; // 等差数列求和:左到右边界内,是公差为1的等差数列,首项+末项 乘以 项数 除以2
}
```
**[$3$、莫比乌斯函数](https://www.cnblogs.com/littlehb/p/16392135.html)**
#### 解题思路
我们分析一下,题目很明了,要求的就是$gcd(x,y)=d$的个数,转化一下:
$$\large x'=x/d,y'=y/d$$
$$\large |gcd(x,y)=d| \Leftrightarrow |gcd(x',y')=1|$$
><font color='red'><b>注</b>:上面的"| |",表示的是符合这样条件的$(x,y)$的数对个数,这两个式子的数对$(x,y)$和$(x',y')$ 是一一对应的。</font>
> **证明**:
首先,$x , y$ 的最大公约数是$d$,那么 **除去最大公约数**,得到的两个数就 **互质**。对于每个$gcd(x,y)=d$都如此操作,那么可以证明,这两个的 **数量** 是一一对应的。
问题转化为:$x≤a/d$ , $y≤b/d$ ,$gcd(x,y)=1$的个数,我们考虑:
<h4>补集思想:互质数 = 总数 - 不互质数</h4>
总数很容易,$a'=a/d,b'=b/d$ ,两个数的所有组合形式数量就是$a'∗b'$,下面分析下我们要减去不互质的数:
$$\large a'b'-⌊\frac{a'}{2}⌋⌊\frac{b'}{2}⌋-⌊\frac{a'}{3}⌋⌊\frac{b'}{3}⌋-...\\
\ \ \ \ \ \ \ +⌊\frac{a'}{6}⌋⌊\frac{b'}{2}⌋+.... \\
\ \ \ \ \ \ \ -.....
$$
这样看来,本题是一个简单的容斥原理!
但是由于容斥原理是$O(N)$的,询问的次数$50000$次,会超时,才会想到需要优化。
**优化办法**
① 莫比乌斯函数
$$\displaystyle \large a' \cdot b'+\sum_{i=1}^{min(a,b)}⌊\frac{a'}{i}⌋⌊\frac{b'}{i}⌋ \cdot {mobius[i]}$$
② 数据分块
参考余数之和。
$$\large \sum_{i=2}^{min(a',b')}⌊\frac{a'}{i}⌋*⌊\frac{b'}{i}⌋*mobius[i]$$
> **证明**:
> 我们要减去的是什么呢?是不互质的数,不互质数是什么呢?是最大公因数不为$1$的数,不为$1$,那可以为几呢?**不是$1$就行**(~~废话~~),**枚举最大公因数不为$1$的个数即可**,枚举每一个可能的最大公因数,并且计算个数,其中 $2 <= i <= min(a',b')$ 。
>
当最大公因数是只有一个质因子的时候,我们可以列出:
$$\large ⌊\frac{a'}{2}⌋* ⌊\frac{b'}{2}⌋+⌊\frac{a'}{3}⌋*⌊\frac{b'}{3}⌋+.....$$
最大公因数是$2$的两对数,实际上就等于$2$的倍数个数相乘,这两个个数是等同的,同理$3$也是,但是我们要考虑重复的情况,如$6$,我们$2,3$,都枚举了一次,那么会重复,根据容斥原理,我们要减去,最后我们发现,其实符号就是$Mobius$函数(这个证明是显然的)。以此类推,枚举到$a′$和$b′$中较小的就可以了,因为大于较小的,则式子为$0$,我们就能推出上面的式子了。
答案呼之欲出:
$$\large a'*b'+\sum_{i=2}^{min(a',b')}⌊\frac{a'}{i}⌋*⌊\frac{b'}{i}⌋*mobius[i]$$
我们把这玩意合体一下
$$\large \sum_{i=1}^{min(a',b')}⌊\frac{a'}{i}⌋*⌊\frac{b'}{i}⌋*mobius[i]$$
- $\large ⌊\frac{a}{i}⌋×⌊\frac{b}{i}⌋$ 对应的序列有很多值都是相同的,计算一次就行(**[数论分块](https://www.cnblogs.com/littlehb/p/16389420.html)** 优化一下),假设值记为$t$,对应的区间假设为$[l, r]$,然后让$t$乘以$μ(l)+μ(l+1)+…+μ(r)$ 即可求出该段中的和,$μ(l)+μ(l+1)+…+μ(r)$,可以使用 **莫比乌斯前缀和** 求解。
### $Code$
```cpp {.line-numbers}
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define endl "\n"
const int N = 50010;
// 线性筛法求莫比乌斯函数(枚举约数)
int mu[N], sum[N]; // 莫比乌斯函数的前缀和
int primes[N], cnt;
bool st[N];
void get_mobius(int n) {
mu[1] = 1;
for (int i = 2; i <= n; i++) {
if (!st[i]) {
primes[cnt++] = i;
mu[i] = -1;
}
for (int j = 0; primes[j] * i <= n; j++) {
int t = primes[j] * i;
st[t] = true;
if (i % primes[j] == 0) {
mu[t] = 0;
break;
}
mu[t] = -mu[i];
}
}
// 维护莫比乌斯函数前缀和
for (int i = 1; i <= n; i++) sum[i] = sum[i - 1] + mu[i];
}
signed main() {
// 筛法求莫比乌斯函数
get_mobius(N - 1);
int T;
cin >> T;
while (T--) {
int a, b, d;
cin >> a >> b >> d;
// 套路啊,满满的套路,直接先用最大公约数a/gcd(a,b)=a',b/gcd(a,b)=b',映射到a',b'
a /= d, b /= d;
// n为 min(a', b')
int n = min(a, b);
int res = 0;
// l r, 是每一段的左右边界
// 每次只能取较小的那个上界作为这一段的右端点r
// 然后下次迭代时下一段的左端点就是r + 1
for (int l = 1, r; l <= n; l = r + 1) { // 分块大法
r = min(n, min(a / (a / l), b / (b / l)));
res += (sum[r] - sum[l - 1]) * (a / l) * (b / l);
}
cout << res << endl;
}
}
```

View File

@@ -0,0 +1,55 @@
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1e4 + 5;
// 例题:HDU4135 HDU2841,HDU1695,HDU3501
LL n, m;
// m 比较大的话可以先打出来素数表再求
// 分解质因数
int p[N], cnt;
void divide(int x) {
cnt = 0;
for (int i = 2; i <= x / i; i++) {
if (x % i == 0) {
p[cnt++] = i;
while (x % i == 0) x /= i;
}
}
if (x > 1) p[cnt++] = x;
}
LL solve() {
LL ans = 0;
for (int i = 1; i < (1 << cnt); i++) { // 二进制枚举质因子所有组合
LL t = 1, cnt = 0;
for (int j = 0; j < cnt; j++) {
if (i >> j & 1) {
t *= p[j];
cnt++;
}
}
ans += (cnt & 1 ? n / t : -n / t); // 奇加偶减
}
return n - ans;
}
int main() {
// 求1~10中与3互质的数的个数
/*
测试用例:
10 3
人脑模拟:1,2,4,5,7,8,10 ,共7个
程序输出结果:7
结论:一致,代码应该是正确的
*/
cin >> n >> m;
// 对m分解质因数
divide(m);
cout << solve() << endl;
return 0;
}

View File

@@ -0,0 +1,71 @@
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define endl "\n"
// 返回1-m中与n互素的数的个数
vector<int> p;
int cal(int n, int m) {
p.clear();
for (int i = 2; i * i <= n; i++) {
if (n % i == 0) {
p.push_back(i);
while (n % i == 0) n /= i;
}
}
if (n > 1) p.push_back(n); // 求n的素因子
int s = 0; // 1到m中与n不互素的数的个数
// 枚举子集,不能有空集,所以从1开始
for (int i = 1; i < 1 << p.size(); i++) { // 从1枚举到(2^素因子个数)
int cnt = 0;
int t = 1;
for (int j = 0; j < p.size(); j++) { // 枚举每个素因子
if (i & (1 << j)) { // 有第i个因子
cnt++; // 计数
t *= p[j]; // 乘上这个质因子
}
}
// 容斥原理
if (cnt & 1) // 选取个数为奇数,加
s += m / t;
else // 选取个数为偶数,减
s -= m / t;
}
return m - s; // 返回1-m中与n互素的数的个数
}
int T, ca;
signed main() {
cin >> T;
while (T--) {
int a, b, c, d, k;
cin >> a >> b >> c >> d >> k;
// k=0时需要特判,因为我们想要 x'=x/k ,y'=y/k,不能随意除,需要判断
if (k == 0) { // k=0时,表示gcd(x,y)=0
/*
如果两个数的最大公约数是0,这意味着这两个数中至少有一个数是0。
因为最大公约数是两个数共有的最大因子,而0没有最大因子,所以0
与任何数的最大公约数都是0。
而a<=x<=b,c<=y<=d,a=c=1,所以,k=0是不可能存在gcd(x,y)=0的,应该返回0对
*/
printf("Case %lld: 0\n", ++ca);
continue;
}
b /= k, d /= k;
// 因为 (1,3)与 (3,1)算1个,所以要限制x<y
// a=c=1
if (b > d) swap(d, b);
int ans = 0;
// d>b
for (int i = 1; i <= d; i++) // 枚举大区间
// c(n,m): 返回1-m中与n互素的数的个数
// 拿大区间[1~d]中的每个数字i,去 [1~b]中找与其互质的数
// 但是,这样做的话,会出现 [1,3],[3,1]这样的情况,为了防止这样的事情发生
// 我们需要控制区间的范围,也就是小于等于i,同时,也要考虑i与b的大小关系,保证i<=b
// 也就是 min(i,b)
ans += cal(i, min(i, b));
printf("Case %lld: %lld\n", ++ca, ans);
}
}

View File

@@ -0,0 +1,51 @@
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define endl "\n"
const int N = 1e6 + 10;
// 返回1-m中与n互素的数的个数
vector<int> p;
int cal(int n, int m) {
p.clear();
for (int i = 2; i * i <= n; i++) {
if (n % i == 0) {
p.push_back(i);
while (n % i == 0) n /= i;
}
}
if (n > 1) p.push_back(n); // 求n的素因子
int s = 0; // 1到m中与n不互素的数的个数
// 枚举子集,不能有空集,所以从1开始
for (int i = 1; i < 1 << p.size(); i++) { // 从1枚举到(2^素因子个数)
int cnt = 0;
int t = 1;
for (int j = 0; j < p.size(); j++) { // 枚举每个素因子
if (i & (1 << j)) { // 有第i个因子
cnt++; // 计数
t *= p[j]; // 乘上这个质因子
}
}
// 容斥原理
if (cnt & 1) // 选取个数为奇数,加
s += m / t;
else // 选取个数为偶数,减
s -= m / t;
}
return m - s; // 返回1-m中与n互素的数的个数
}
signed main() {
int T;
cin >> T;
while (T--) {
int a, b;
cin >> a >> b;
int res = 0;
// 从1~n(b现在就是n)之间,找到所有与m(m现在就是i)互质的数字个数
for (int i = 1; i <= a; i++) res += cal(i, b);
printf("%lld\n", res);
}
}

View File

@@ -0,0 +1,90 @@
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1e5 + 10;
// 性能非常棒,但是没看懂,等功能加深后再回来看看。
// 据说这道题,还可以用莫比乌斯反演来做,以后再
// 筛法求欧拉函数
int primes[N]; //保存的是每一个质数
int cnt; // cnt代表质数下标,就是到第几个了
int phi[N]; //欧拉函数值,一般叫Φ,函数名不能是希腊字母,所以转为phi
bool st[N]; //代表是不是已经被筛掉了
LL res; //结果
void get_eulers(int n) {
// 1的欧拉函数值
phi[1] = 1;
//如果当前i没有被筛过,说明当前i是一个质数
for (int i = 2; i <= n; i++) {
if (!st[i]) {
//增加一个质数
primes[cnt++] = i;
phi[i] = i - 1;
}
for (int j = 0; primes[j] <= n / i; j++) {
int t = primes[j] * i;
st[t] = true;
if (i % primes[j] == 0) {
phi[t] = phi[i] * primes[j];
break;
} else
phi[t] = phi[i] * (primes[j] - 1);
}
}
}
int n, m;
int num, p[10], sum, i;
void dfs(int pos, int lcm, int id) {
if (id == 0) {
if (i & 1)
num += (m / lcm);
else
num -= (m / lcm);
return;
}
if (pos > sum) return;
if (lcm * p[pos] <= m)
dfs(pos + 1, lcm * p[pos], id - 1);
dfs(pos + 1, lcm, id);
return;
}
int solve(int k) {
sum = 0;
//下面是分解质数
for (int i = 2; i <= k / i; i++) {
if (k % i == 0) {
p[++sum] = i;
while (k % i == 0) k /= i;
}
}
if (k > 1) p[++sum] = k;
for (i = 1; i <= sum; i++) dfs(1, 1, i);
return m - num;
}
int main() {
//筛法求欧拉函数
get_eulers(N - 10);
int T;
scanf("%d", &T);
while (T--) {
scanf("%d%d", &n, &m); // n行m列
if (m > n) swap(n, m);
LL res = 1;
for (int i = 2; i <= m; i++) res += 2 * phi[i]; //这是啥意思呢?
for (int i = m + 1; i <= n; i++) {
num = 0;
res += solve(i);
}
printf("%lld\n", res);
}
return 0;
}

View File

@@ -0,0 +1,64 @@
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define endl "\n"
const int mod = 1000000007;
signed main() {
int m;
while (cin >> m && m) {
if (m == 1) {
cout << "0" << endl;
// 边界需要特别注意!本题的左边界是1
// 表示的含义是小于1,并且与1不互质的数的加法和,当然是0。
// 在做题时,先想正常思路,然后再思考一下边界是不是用特判。
continue;
}
int n = m; // 复制出来进行质因数分解
// 分解质因数
vector<int> p;
for (int i = 2; i * i <= n; i++) {
if (n % i == 0) {
p.push_back(i);
while (n % i == 0) n = n / i;
}
}
if (n > 1) p.push_back(n);
// 容斥原理
int s = 0;
for (int i = 1; i < (1 << p.size()); i++) {
int cnt = 0;
int t = 1;
for (int j = 0; j < p.size(); j++) {
if (i >> j & 1) {
cnt++;
t *= p[j];
}
}
/* t:质因子的累乘积
cnt:质因子的个数
举栗子:
枚举到的组合中只有数字2,那么2,4,6,8,10,....需要加上
枚举到的组合中只有数字3,那么3,6,9,12,15,....需要加上
枚举到的组合中中6.有数字2,3,那么6,12,18,....需要减去
*/
int num = (m - 1) / t; // 题目要求:比m小的数字中,也就是[1~m-1]这些数字中有多少个数是t的倍数呢?是(m-1)/t个。
/* 这些数字,首个是t,第二个是t*2,最后一个是t*num
等差数列求和公式:(首项+末项)*项数/2
模板题是计数,这里不是计数,而是计算这些数加在一起是多少,不能傻傻的一个个累加,而是采用了数学中的等差数列求和公式,
否则聪明的高斯该生气了~
*/
int tmp = (t + t * num) * num / 2;
if (cnt & 1) // 奇数的加
s += tmp;
else // 偶数的减
s -= tmp;
}
cout << s % mod << endl; // 取模输出
}
}

View File

@@ -0,0 +1,46 @@
#include <bits/stdc++.h>
using namespace std;
long long fac[500];
int cnt;
void fen(int n) {
for (int i = 2; i * i <= n; i++) {
if (n % i == 0) {
fac[cnt++] = i;
while (n % i == 0) n /= i;
}
}
if (n > 1) fac[cnt++] = n;
} //素因子分解
long long ans;
int n;
const int mod = 1000000007;
void dfs(int w, int id, long long sum) {
for (int i = w; i < cnt; i++) {
long long ss = sum * fac[i];
if (id & 1) {
int tmp = n / ss;
ans += ss * (1 + tmp) * tmp / 2;
ans %= mod;
} else {
int tmp = n / ss;
ans -= ss * (1 + tmp) * tmp / 2;
ans = (ans + mod) % mod;
}
dfs(i + 1, id + 1, ss);
}
} //递归实现容斥原理
int main() {
while (scanf("%d", &n) != EOF) {
if (n == 0) break;
if (n == 1) {
cout << 0 << endl;
continue;
}
cnt = 0;
fen(n);
ans = 0;
dfs(0, 1, 1);
cout << (ans % mod - n + mod) % mod << endl;
}
return 0;
}

View File

@@ -0,0 +1,26 @@
#include <bits/stdc++.h>
using namespace std;
typedef unsigned long long ULL;
const ULL mod = 1000000007;
ULL euler(ULL n) {
ULL res = n, a = n;
for (ULL i = 2; i * i <= a; i++) {
if (a % i == 0) {
res = res / i * (i - 1);
while (a % i == 0) a /= i;
}
}
if (a > 1) res = res / a * (a - 1);
return res;
}
int main() {
ULL n;
while (~scanf("%llu", &n), n) {
ULL sum = n * (1 + n) / 2 - n, ans;
ans = sum - euler(n) * n / 2;
printf("%llu\n", ans % mod);
}
return 0;
}

View File

@@ -0,0 +1,51 @@
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define endl "\n"
vector<int> p; // 质数数组
int cal(int n, int m) { // 返回1-m中与n互素的数的个数
p.clear(); // 多组测试数据,清空
// 分解质因数
for (int i = 2; i * i <= n; i++) { // 对n分解质因数
if (n % i == 0) {
p.push_back(i);
while (n % i == 0) n /= i;
}
}
if (n > 1) p.push_back(n); // 最后一个大因子,也加入
int s = 0; // 1到m中与n不互素的数的个数
// 枚举子集,不能有空集,所以从1开始
for (int i = 1; i < 1 << p.size(); i++) { // 从1枚举到(2^素因子个数)
int cnt = 0;
int t = 1;
for (int j = 0; j < p.size(); j++) { // 枚举每个素因子
if (i & (1 << j)) { // 有第i个因子
cnt++; // 计数
t *= p[j]; // 乘上这个质因子
}
}
// 容斥原理
if (cnt & 1) // 选取个数为奇数,加
s += m / t;
else // 选取个数为偶数,减
s -= m / t;
}
return m - s; // 返回1-m中与n互素的数的个数
}
signed main() {
int T, ca = 0;
cin >> T;
while (T--) {
int m, a, b;
cin >> a >> b >> m; // 求区间[a,b]中与m互素的数字个数
// 计算[1,a-1]之间与m互素的个数
// 计算[1, b]之间与m互素的个数
int ans = cal(m, b) - cal(m, a - 1);
printf("Case #%d: %lld\n", ++ca, ans);
}
}

View File

@@ -0,0 +1,85 @@
#include <bits/stdc++.h>
using namespace std;
const int mod = 1000007;
const int N = 410;
int C[N][N];
int n, m, k, ans;
/*
Sample Input
2
2 2 1
2 3 2
Sample Output
Case 1: 0
Case 2: 2
*/
int main() {
#ifndef ONLINE_JUDGE
freopen("UVA11806.in", "r", stdin);
#endif
// 预处理出组合数结果数组
for (int i = 1; i < N; i++) {
C[i][0] = C[i][i] = 1;
for (int j = 1; j < i; j++)
C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % mod;
}
int T, cas = 1;
int S, s1, s2, s3, s4;
cin >> T;
while (T--) {
ans = 0; // 多组测试数据,每次注意清零
cin >> n >> m >> k; // n行,m列,k个人
if (k == 0) { // 一定要注意边界情况,比如0个人
printf("Case %d: 0\n", cas++);
continue;
}
S = C[n * m][k]; // n*m个格子中找出k个格子站人,就是所有方案数
/*
S为总数
A为第一行没有站人
B为最后一行没有站人
C为第一列没有站人
D为最后一列没有站人
令:
s1 =(A+B+C+D)
s2=(AB+AC+AD+BC+BD+CD)
s3=(ABC+ABD+ACD+BCD)
s4=(ABCD)
*/
// A:第一行没人,即C[(n-1)*m,k]
// B:最后一行没人,即C[(n-1)*m,k]
// C:第一列没人,即:C[n*(m-1),k]
// D:最后一列没人,即:C[n*(m-1),k]
s1 = 2 * (C[n * (m - 1)][k] + C[(n - 1) * m][k]) % mod;
// AB:第一行,最后一行没有人,行少了2行,列不变,即C[(n-2)*m,k]
// AC:第一行,第一列没有人,行少了1行,列少了一列,即C[(n-1)*(m-1),k]
// AD:第一行,最后一列没有人,即C[(n-1)*(m-1),k]
// BC:最后一行,第一列没有人,即C[(n-1)*(m-1),k]
// BD:最后一行,最后一列没有人,即C[(n-1)*(m-1),k]
// CD:第一列,最后一列没有人,即C[n*(m-2),k]
// 中间4个是一样的:4*C[(n-1)*(m-1),k]
s2 = (C[(n - 2) * m][k] + 4 * C[(n - 1) * (m - 1)][k] + C[n * (m - 2)][k]) % mod;
// ABC:第一行、最后一行、第一列没有人,行少了2行,列少了1列,即C[(n-2)*(m-1),k]
// ABD:第一行、最后一行、最后一列没有人,行少了2行,列少了1列,即C[(n-2)*(m-1),k]
// ABC+ABD=2*C[(n-2)*(m-1),k]
// ACD:第一行、第一列、最后一列没有人,行少了1行,列少了2列,即 C[(n-1)*(m-2),k]
// BCD:最后一行、第一列、最后一列没有人,行少了1行,列少了2列,即 C[(n-1)*(m-2),k]
// ACD+BCD=2*C[(n-1)*(m-2),k]
s3 = 2 * (C[(n - 2) * (m - 1)][k] + C[(n - 1) * (m - 2)][k]) % mod;
// 第一行,第一列,最后一行,最后一列都不能站,那么,剩下n-2行,m-2列,需要在(n-2)*(m-2)这么多的格子里找出k个格子
s4 = C[(n - 2) * (m - 2)][k] % mod;
ans = (S - s1 + s2 - s3 + s4) % mod; // 容斥原理
ans = (ans + mod) % mod; // 防止取余后出现负数
printf("Case %d: %d\n", cas++, ans); // 输出答案
}
return 0;
}

View File

@@ -0,0 +1,3 @@
2
2 2 1
2 3 2