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

129
TangDou/AcWing/Sort/785.md Normal file
View File

@@ -0,0 +1,129 @@
## [$AcWing$ $785$. 快速排序](https://www.acwing.com/problem/content/787/)
### 一、题目描述
给定你一个长度为 $n$ 的整数数列。
请你使用快速排序对这个数列按照从小到大进行排序。
并将排好序的数列按顺序输出。
**输入格式**
输入共两行,第一行包含整数 $n$。
第二行包含 $n$ 个整数(所有整数均在 $1∼109$ 范围内),表示整个数列。
**输出格式**
输出共一行,包含 $n$ 个整数,表示排好序的数列。
**数据范围**
$1≤n≤100000$
**输入样例:**
```cpp {.line-numbers}
5
3 1 2 4 5
```
**输出样例:**
```cpp {.line-numbers}
1 2 3 4 5
```
### 二、算法原理
* 每次排序时选取一个基准,将小于基准的数全部放到基准点的左边,将大于或等于基准的数全部放到基准的右边。
* 完成后,得到了两个 **整体** 上有序的子数组,再递归继续,直至所有元素完成排序。
#### 算法模拟
假设对$6, 1, 2, 7, 9, 3, 4, 5,10, 8$这$10$个数进行排序:
**本次模拟的基准是以数组的第一个元素为基准的。**
从后向前扫描,$i$去向右找第一个大于 **基准$6$** (**这个$6$是选择的第一元素,代码中我们将选择中间位置的元素,道理都是一样的**) 的数字,$j$向左查找第一个小于基准$6$数字:
<center><img src='https://images2017.cnblogs.com/blog/1230454/201801/1230454-20180124094950569-1570587181.png'></center>
找到$7$和$5$,交换$7$和$5$
<center><img src='https://images2017.cnblogs.com/blog/1230454/201801/1230454-20180124095142850-1884986244.png'></center>
继续前进,找到$9$和$4$,然后交换
<center><img src='https://images2017.cnblogs.com/blog/1230454/201801/1230454-20180124095407725-16725917.png'></center>
$i>=j$,交换$6$和$3$
<center><img src='https://images2017.cnblogs.com/blog/1230454/201801/1230454-20180124095611490-180314539.png'></center>
交换之后:$3, 1, 2, 5, 4, 6, 9, 7, 10, 8$, 一趟排序结束。
以基准值$6$划分的两堆数据,左手边都比$6$小,右手边都比$6$大,$6$在中间,然后再用同样的办法递归处理左右两边即可,此时,$6$就不参与了。
### 三、实现代码
```cpp {.line-numbers}
#include <bits/stdc++.h>
using namespace std;
const int N = 100010;
int q[N];
int n;
/**
* 功能:利用快速排序对数组进行排序
* @param q 要排序的数组
* @param l 左端下标位置
* @param r 右端下标位置
*/
void quick_sort(int q[], int l, int r) {
if (l >= r) return;
int i = l - 1, j = r + 1; // 从界外开始
int x = q[(l + r) >> 1]; // 基准值为中间位置的数值
while (i < j) { // 没有碰面就一直按这个逻辑跑一跑
do i++;
while (q[i] < x); // i哨兵向右走,找到第一个大于基准值的数,这个数的位置需要调整
do j--;
while (q[j] > x); // j哨兵向左走,找到第一个小于基准值的数,这个数的位置需要调整
if (i < j) swap(q[i], q[j]); // 如果还没有碰面,就对调一下
}
// 当退出上面的循环,就是两个人碰面的时候,此时左右两半都已经是有序的了,再递归处理
quick_sort(q, l, j), quick_sort(q, j + 1, r);
}
int main() {
cin >> n;
for (int i = 1; i <= n; i++) cin >> q[i];
quick_sort(q, 1, n);
for (int i = 1; i <= n; i++) printf("%d ", q[i]);
return 0;
}
```
### 四、快速排序算法的证明与边界分析 【了解】
![](http://dsideal.obs.cn-north-1.myhuaweicloud.com/HuangHai/BlogImages/2023/02/7e7eb50d4f54518144a7a131e8050fc3.png)
<font color='red' size=4><b>琢磨太深,使人痛苦!</b></font>
**[参考文档](https://www.acwing.com/solution/content/16777/)**
**顺带一提用$i$做划分时的模板**
```cpp {.line-numbers}
void quick_sort(int q[], int l, int r) {
if (l >= r) return;
int i = l - 1, j = r + 1;
int x = q[(l + r + 1) >> 1]; // 非得要用i这里要修改!
while (i < j) {
do i++;
while (q[i] < x);
do j--;
while (q[j] > x);
if (i < j) swap(q[i], q[j]);
}
quick_sort(q, l, i - 1), quick_sort(q, i, r); // 非得要用i这里要修改!
}
```

View File

@@ -0,0 +1,29 @@
#include <bits/stdc++.h>
using namespace std;
const int N = 100010;
int q[N];
int n;
void quick_sort(int q[], int l, int r) {
if (l >= r) return;
int i = l - 1, j = r + 1;
int x = q[(l + r) >> 1];
while (i < j) {
do i++;
while (q[i] < x);
do j--;
while (q[j] > x);
if (i < j) swap(q[i], q[j]);
}
quick_sort(q, l, j), quick_sort(q, j + 1, r);
}
int main() {
cin >> n;
for (int i = 1; i <= n; i++) cin >> q[i];
quick_sort(q, 1, n);
for (int i = 1; i <= n; i++) printf("%d ", q[i]);
return 0;
}

View File

@@ -0,0 +1,31 @@
#include <bits/stdc++.h>
using namespace std;
const int N = 100010;
int q[N];
int n;
void quick_sort(int q[], int l, int r) {
if (l >= r) return;
int i = l - 1, j = r + 1;
int x = q[(l + r + 1) >> 1]; // 非得要用i这里要修改!
while (i < j) {
do i++;
while (q[i] < x);
do j--;
while (q[j] > x);
if (i < j) swap(q[i], q[j]);
}
quick_sort(q, l, i - 1), quick_sort(q, i, r); // 非得要用i这里要修改!
}
int main() {
cin >> n;
for (int i = 1; i <= n; i++) cin >> q[i];
quick_sort(q, 1, n);
for (int i = 1; i <= n; i++) printf("%d ", q[i]);
return 0;
}

View File

@@ -0,0 +1,31 @@
#include <bits/stdc++.h>
using namespace std;
const int N = 100010;
int q[N];
int n, k;
void quick_sort(int q[], int l, int r, int k) {
if (l >= r) return;
int i = l - 1, j = r + 1, x = q[(l + r) >> 1];
while (i < j) {
do i++;
while (q[i] < x);
do j--;
while (q[j] > x);
if (i < j) swap(q[i], q[j]);
}
int len = j - l + 1; // 左侧长度
if (k <= len)
quick_sort(q, l, j, k); // 左侧
else
quick_sort(q, j + 1, r, k - len); // 右侧
}
int main() {
cin >> n >> k;
for (int i = 1; i <= n; i++) cin >> q[i];
quick_sort(q, 1, n, k);
printf("%d", q[k]);
return 0;
}

View File

@@ -0,0 +1,74 @@
## [$AcWing$ $786$. 第$k$个数](https://www.acwing.com/problem/content/description/788/)
### 一、题目描述
给定一个长度为 $n$ 的整数数列,以及一个整数 $k$,请用快速选择算法求出数列**从小到大排序** 后的第 $k$ 个数。
**输入格式**
第一行包含两个整数 $n$ 和 $k$。
第二行包含 $n$ 个整数(所有整数均在 $1∼109$ 范围内),表示整数数列。
**输出格式**
输出一个整数,表示数列的第 $k$ 小数。
**数据范围**
$1≤n≤100000,1≤k≤n$
**输入样例:**
```cpp {.line-numbers}
5 3
2 4 1 5 3
```
**输出样例:**
```cpp {.line-numbers}
3
```
### 二、理解感悟
1、这是[快速排序](https://www.cnblogs.com/littlehb/p/15000855.html)模板的练习题。
2、不一样的地方在于它可以利用快排模板,但却不需要真的把所有数据排序完成,每次一分为二后,只关心自己所有的那一半,就是可以节约一半的递归时间。
3、由于是关心 **位置**(第几个),所以在递归时需要携带这个参数。
4、位置这个参数 **不是一成不变的**,因为如果在左侧,那么就是原来的位置,如果在右侧,那就需要减去整个左侧的长度。这个 $k$参数可以理解为 **在当前数组中的位置**,最终将确定这个位置上的值。
5、最后,直接使用$q[k]$就是拿到了最终这个位置上应该存在的值。
### 三、实现代码
```cpp {.line-numbers}
#include <bits/stdc++.h>
using namespace std;
const int N = 100010;
int q[N];
int n, k;
void quick_sort(int q[], int l, int r, int k) {
if (l >= r) return;
int i = l - 1, j = r + 1, x = q[(l + r) >> 1];
while (i < j) {
do i++;
while (q[i] < x);
do j--;
while (q[j] > x);
if (i < j) swap(q[i], q[j]);
}
int len = j - l + 1; // 左侧长度
if (k <= len)
quick_sort(q, l, j, k); // 左侧
else
quick_sort(q, j + 1, r, k - len); // 右侧
}
int main() {
cin >> n >> k;
for (int i = 1; i <= n; i++) cin >> q[i];
quick_sort(q, 1, n, k);
printf("%d", q[k]);
return 0;
}
```

View File

@@ -0,0 +1,34 @@
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int q[N], t[N];
void merge_sort(int q[], int l, int r) {
if (l >= r) return;
int mid = (l + r) >> 1;
merge_sort(q, l, mid), merge_sort(q, mid + 1, r);
int i = l, j = mid + 1, k = 0;
while (i <= mid && j <= r)
if (q[i] <= q[j])
t[k++] = q[i++];
else
t[k++] = q[j++];
while (i <= mid) t[k++] = q[i++];
while (j <= r) t[k++] = q[j++];
for (i = l, j = 0; i <= r; i++, j++) q[i] = t[j];
}
int main() {
int n;
cin >> n;
for (int i = 1; i <= n; i++) cin >> q[i];
merge_sort(q, 1, n);
for (int i = 1; i <= n; i++) printf("%d ", q[i]);
return 0;
}

View File

@@ -0,0 +1,76 @@
## [$AcWing$ $787$. 归并排序](https://www.acwing.com/problem/content/description/789/)
### 一、题目描述
给定你一个长度为 $n$ 的整数数列。
请你使用 **归并排序** 对这个数列按照从小到大进行排序。
并将排好序的数列按顺序输出。
**输入格式**
输入共两行,第一行包含整数 $n$。
第二行包含 $n$ 个整数(所有整数均在 $1∼10^9$ 范围内),表示整个数列。
**输出格式**
输出共一行,包含 $n$ 个整数,表示排好序的数列。
**数据范围**
$1≤n≤100000$
**输入样例:**
```cpp {.line-numbers}
5
3 1 2 4 5
```
**输出样例:**
```cpp {.line-numbers}
1 2 3 4 5
```
### 二、算法原理
<img src='https://img2020.cnblogs.com/blog/8562/202109/8562-20210903164156199-2103299534.gif'>
### 二、实例模拟
具体的我们以一组无序数列$\{14,12,15,13,11,16 \}$为例分解说明,如下图所示:
<img src='https://img2020.cnblogs.com/blog/8562/202109/8562-20210903164202788-1805391625.jpg'>
上图中首先把一个未排序的序列从中间分割成$2$部分,再把$2$部分分成$4$部分,依次分割下去,直到分割成一个一个的数据,再把这些数据两两归并到一起,使之有序,不停的归并,最后成为一个排好序的序列。
### 三、代码模板
```cpp {.line-numbers}
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int q[N], t[N];
void merge_sort(int q[], int l, int r) {
if (l >= r) return;
int mid = (l + r) >> 1;
merge_sort(q, l, mid), merge_sort(q, mid + 1, r);
int i = l, j = mid + 1, k = 0;
while (i <= mid && j <= r)
if (q[i] <= q[j])
t[k++] = q[i++];
else
t[k++] = q[j++];
while (i <= mid) t[k++] = q[i++];
while (j <= r) t[k++] = q[j++];
for (i = l, j = 0; i <= r; i++, j++) q[i] = t[j];
}
int main() {
int n;
cin >> n;
for (int i = 1; i <= n; i++) cin >> q[i];
merge_sort(q, 1, n);
for (int i = 1; i <= n; i++) printf("%d ", q[i]);
return 0;
}
```

View File

@@ -0,0 +1,2 @@
5
10 8 7 9 4

151
TangDou/AcWing/Sort/788.md Normal file
View File

@@ -0,0 +1,151 @@
##[$AcWing$ $788$. 逆序对的数量](https://www.acwing.com/problem/content/790/)
### 一、题目描述
给定一个长度为 $n$ 的整数数列,请你计算数列中的逆序对的数量。
逆序对的定义如下:对于数列的第 $i$ 个和第 $j$ 个元素,如果满足 $i<j$
且 $a[i]>a[j]$,则其为一个逆序对;否则不是。
**输入格式**
第一行包含整数 $n$,表示数列的长度。
第二行包含 $n$ 个整数,表示整个数列。
**输出格式**
输出一个整数,表示逆序对的个数。
**数据范围**
$1≤n≤100000$,数列中的元素的取值范围 $[1,10^9]$
**输入样例:**
```cpp {.line-numbers}
6
2 3 4 5 6 1
```
**输出样例:**
```cpp {.line-numbers}
5
```
### 二、什么是逆序对?
大的在前,小的在后,这样有一个算一个。
<center><img src='https://img2020.cnblogs.com/blog/8562/202109/8562-20210906094343981-907775927.png'></center>
逆序对包括:$\{5,2\},\{5,2\},\{5,1\},\{5,4\},\{2,1\},\{2,1\}$,共$6$个。
### 三、暴力大法
```cpp {.line-numbers}
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
int n;
const int N = 100010;
int a[N];
LL ans;
//暴力大法
/*测试数据
5
5 2 2 1 4
*/
int main() {
cin >> n;
for (int i = 1; i <= n; i++)cin >> a[i];
//暴力大法
for (int i = 1; i <= n; i++)
for (int j = i + 1; j <= n; j++)
if (a[i] > a[j]) ans++;
cout << ans << endl;
return 0;
}
```
### 四、答案的数据类型
最终的$ans$定义为什么类型?就是说给定范围内的数据,产生的逆序对最多是多少个呢?本题中$n<=100000$,就是小于$10^5$。那么什么情况下逆序对最多呢?就是完全倒序的情况下逆序对最多,那么在有$n$个数据的情况下,有多少个逆序对呢?
对于第$1$个而言,有$n-1$个逆序对,第$2$个有$n-2$个逆序对,....,第$n$个有$0$个逆序对。
$ans=(n-1)+(n-2)+(n-3)+...+1=\frac{n*(n-1)}{2}$
本题$n$最大是$10^5$,所以$ans$最大值是$ans=100000*(100000-1)/2 \approx 1e{10} \div 2=5*10^9$
而$int$的极限是$2147483647$,比$2e9$大一点点,就是说$int$装不下,需要用$long \ long$:
**<center><font color='red'>十年$OI$一场空,不开$long\ long$ 见祖宗</font></center>**
毫无悬念,$tle$妥妥的。
### 五、多种解法对比
| | 归并排序 | 树状数组 | 线段树+静态数组 | 线段树+离散化+$STL$ | 线段树+离散化+手写二分 |
| -------- | -------- | -------- | --------------- | ------------------- | ---------------------- |
| 执行时长 | $75 ms$ | 待测 | $344 ms$ | $342 ms$ | $284ms$ |
| $AcWing$ | $√$ | $√$ | $√$ | $√$ | $√$ |
| 洛谷 | $√$ | $√$ | $√$ | $√$ | $√$ |
<font color='red'>**总结:STL的二分常数较大,尽量手写二分答题,减少TLE机会。**</font>
### 六、利用归并求逆序对
![](https://img2020.cnblogs.com/blog/8562/202109/8562-20210906092723132-114263908.png)
$Q$:为什么归并排序可以求逆序对呢?
答:在归并两个子数组时,有三种情况:
**(1)、逆序对在左边(红色)**
这在调用左侧子数组进行递归时,已经完成了统计,自己不管。(内部问题内部解决)
**(2)、逆序对在右边(绿色)**
这在调用右侧子数组进行递归时,已经完成了统计,自己不管。(内部问题内部解决)
**(3)、逆序对在左侧和右侧(黄色)**
这个需要自己来处理,对于右侧的黄色圆,那么在左侧的黄色圆及左侧黄色圆后面,一直到中线的所有数,都是比右侧黄色圆大的,就是有$mid-i+1$个逆序对。
可以结合代码来理解原理,其实就是在归并的时刻,只处理合并数组中大小反着的两个数字,一旦发现前面的数字比后面的数字大,就是发现了一个逆序对,同时,由于归并排序的特点,两个要归并的数组是内部有序的,所以,意味着左侧当前数字及左侧当前数字一直到左侧集合结束的所有数字,都比右侧当前数字大!个数就是$mid-i+1$个,其中$l<=i<=mid$。
### 七、归并代码
```cpp {.line-numbers}
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 100010;
int n; // n个数据
int q[N]; // 原数组
int t[N]; // 临时数组
LL res; // 结果
void merge_sort(int q[], int l, int r) {
if (l >= r) return;
int mid = (l + r) >> 1;
merge_sort(q, l, mid);
merge_sort(q, mid + 1, r);
int i = l, j = mid + 1, k = 0;
while (i <= mid && j <= r)
if (q[i] <= q[j])
t[k++] = q[i++];
else {
t[k++] = q[j++];
res += mid - i + 1;
}
while (i <= mid) t[k++] = q[i++];
while (j <= r) t[k++] = q[j++];
for (i = l, j = 0; i <= r; i++, j++) q[i] = t[j];
}
int main() {
cin >> n;
for (int i = 1; i <= n; i++) cin >> q[i];
merge_sort(q, 1, n);
printf("%d\n", res);
return 0;
}
```
### 八、树状数组解法
**[因为问题比较复杂,另开一个博文水一篇题解](https://www.cnblogs.com/littlehb/p/17143537.html)**
### 九、线段树+静态数组
**[因为问题比较复杂,另开一个博文水一篇题解](https://www.cnblogs.com/littlehb/p/17143537.html)**

View File

@@ -0,0 +1,36 @@
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 100010;
int n; // n个数据
int q[N]; // 原数组
int t[N]; // 临时数组
LL ans; // 结果
void merge_sort(int q[], int l, int r) {
if (l >= r) return;
int mid = (l + r) >> 1;
merge_sort(q, l, mid), merge_sort(q, mid + 1, r);
int i = l, j = mid + 1, k = 0;
while (i <= mid && j <= r)
if (q[i] <= q[j])
t[k++] = q[i++];
else {
t[k++] = q[j++];
ans += mid - i + 1;
}
while (i <= mid) t[k++] = q[i++];
while (j <= r) t[k++] = q[j++];
for (i = l, j = 0; i <= r; i++, j++) q[i] = t[j];
}
int main() {
cin >> n;
for (int i = 1; i <= n; i++) cin >> q[i];
merge_sort(q, 1, n);
printf("%lld\n", ans);
return 0;
}

View File

@@ -0,0 +1,23 @@
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
int n;
const int N = 100010;
int a[N];
LL ans;
// 暴力法
/*测试数据
5
5 2 2 1 4
*/
int main() {
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i];
// 暴力大法
for (int i = 1; i <= n; i++)
for (int j = i + 1; j <= n; j++)
if (a[i] > a[j]) ans++;
cout << ans << endl;
return 0;
}

View File

@@ -0,0 +1,11 @@
#include <bits/stdc++.h>
using namespace std;
int a[11] = {0, 5, 8, 4, 12, 6, 8, 9, 5, 10, 3};
int main() {
sort(a + 1, a + 1 + 10); // 排序
int al = unique(a + 1, a + 1 + 10) - a; // 去重
for (int i = 1; i <= al; i++) cout << "a[" << i << "]=" << a[i] << " "; // 输出
return 0;
}

View File

@@ -0,0 +1,92 @@
#include <bits/stdc++.h>
using namespace std;
const int N = 500010;
int n;
int a[N], b[N];
// 输出原数组
void out() {
for (int i = 1; i <= n; i++) printf("%d ", a[i]);
puts("");
}
// 功能:排序办法
// ①、数小的在前面
// ②、如果数一样大,序号小的在前
bool cmp(int x, int y) {
if (a[x] == a[y]) return x < y;
return a[x] < a[y];
}
// 【方法1】
void func1() {
cin >> n;
// ① 初始化 : b[i]=a[i]
// ② 排序(小的在前,大的在后) : a[]
// ③ 每个b[i]通过二分,在排序后的a[]中查找位置,记录b[i]=找到的位置
// ④ a[]数组被重新由小到大排序
for (int i = 1; i <= n; i++) cin >> a[i], b[i] = a[i];
// 输出原始数组
out();
// 原始数组由小到大排序
// ① 由小到大排序,不用加什么greater<int>(),代码更简单
// ② 只有由小到大排序,后面使用二分进行数值查询位置时,才能使用lower_bound二分方法
sort(a + 1, a + n + 1);
// 离散化,遍历原始数组b[i],找出它的排名,即b[2]=3表示第2个输入的是排名第3的
// 这里的离散化没有用到去重,如果需要去重,参考:788_Unique.cpp
for (int i = 1; i <= n; i++) b[i] = lower_bound(a + 1, a + n + 1, b[i]) - a;
// 倒序,由大到小(视业务调整)
for (int i = n; i; i--) printf("%d ", b[i]);
}
// 【方法2】
void func2() {
cin >> n;
// ① 初始化 :b[i]=i
// ② 排序(小的在前,大的在后) : b[]
// ③ a[]数组不动
for (int i = 1; i <= n; i++) cin >> a[i], b[i] = i;
// 输出原始数组
out();
// 对b[]排序
sort(b + 1, b + n + 1, cmp);
// 倒序,由大到小(视业务调整)
for (int i = n; i; i--) printf("%d ", b[i]);
}
/*
原始数组 10 8 7 9 4
输出 1 4 2 3 5
含义:
(1)最大的是原始数组的a[1]=10
(2)次大的是原始数组的a[4]=9
(3)三大的是原始数组的a[2]=8
...
*/
int main() {
// 调用方法1进行测试
freopen("788.in", "r", stdin);
func1();
puts("");
// 在第二次重定向时修改一下cin的状态
cin.clear();
puts("");
// 调用方法2进行测试
freopen("788.in", "r", stdin);
func2();
puts("");
return 0;
}

View File

@@ -0,0 +1,49 @@
#include <bits/stdc++.h>
using namespace std;
const int N = 500010;
typedef long long LL;
LL ans;
int n;
// 似乎结构体对于线段树不友好,不准备采用
struct Node {
int val;
int id;
} b[N];
bool cmp(Node a, Node b) {
if (a.val == b.val) return a.id < b.id; // 值相等时,号小的在前
return a.val < b.val; // 值小的在前
}
int tr[N];
int lowbit(int x) {
return x & -x;
}
void add(int x, int c) {
for (int i = x; i <= n; i += lowbit(i)) tr[i] += c;
}
int sum(int x) {
int res = 0;
for (int i = x; i; i -= lowbit(i)) res += tr[i];
return res;
}
int main() {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> b[i].val;
b[i].id = i;
}
sort(b + 1, b + 1 + n, cmp);
for (int i = 1; i <= n; i++) {
add(b[i].id, 1);
ans += sum(n) - sum(b[i].id); // 因为是值小的提前进场,如果值一样,则号小的先进场,那么发现有值比我小,号比我大的,就是逆序了
}
printf("%lld\n", ans);
return 0;
}

View File

@@ -0,0 +1,78 @@
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 5e5 + 10;
int n;
// 似乎结构体对于线段树不友好,不准备采用
struct Node {
int val;
int id;
} d[N];
bool cmp(Node a, Node b) {
if (a.val == b.val) return a.id < b.id;
return a.val < b.val;
}
// 线段树+区间和模板
#define ls u << 1
#define rs u << 1 | 1
struct Tree {
int l, r;
int sum;
} tr[N << 2];
void build(int u, int l, int r) {
tr[u].l = l, tr[u].r = r;
if (l == r) return;
int mid = l + r >> 1;
build(ls, l, mid), build(rs, mid + 1, r);
}
void pushup(int u) {
tr[u].sum = tr[ls].sum + tr[rs].sum;
}
void modify(int u, int x, int v) {
if (tr[u].l == x && tr[u].r == x) {
tr[u].sum += v;
return;
}
int mid = tr[u].l + tr[u].r >> 1;
if (x <= mid)
modify(ls, x, v);
else
modify(rs, x, v);
pushup(u);
}
int query(int u, int l, int r) {
if (l > r) return 0;
if (tr[u].l == l && tr[u].r == r) return tr[u].sum;
int mid = tr[u].l + tr[u].r >> 1;
if (mid < l) return query(rs, l, r);
if (r <= mid) return query(ls, l, r);
return query(ls, l, mid) + query(rs, mid + 1, r);
}
LL ans;
int main() {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> d[i].val;
d[i].id = i;
}
sort(d + 1, d + 1 + n, cmp);
build(1, 1, n);
for (int i = 1; i <= n; i++) {
modify(1, d[i].id, 1);
ans += query(1, d[i].id + 1, n);
}
printf("%lld\n", ans);
return 0;
}

View File

@@ -0,0 +1,82 @@
// 权值线段树解法
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 5e5 + 10;
int a[N], b[N];
int n;
// 线段树区间和模板
#define ls u << 1
#define rs u << 1 | 1
struct Node {
int l, r; // 范围
int sum; // 区间内数字个数
} tr[N << 2]; // 4倍空间
// 创建
void build(int u, int l, int r) {
tr[u].l = l, tr[u].r = r;
if (l == r) return;
int mid = l + r >> 1;
build(ls, l, mid), build(rs, mid + 1, r);
}
// 向上更新统计信息
void pushup(int u) {
tr[u].sum = tr[ls].sum + tr[rs].sum;
}
// 修改点的值
void modify(int u, int x, int v) {
if (tr[u].l == x && tr[u].r == x) {
tr[u].sum += v;
return;
}
int mid = tr[u].l + tr[u].r >> 1;
if (x <= mid)
modify(ls, x, v);
else
modify(rs, x, v);
pushup(u);
}
// 查询区间和
int query(int u, int l, int r) {
if (l > r) return 0;
if (tr[u].l == l && tr[u].r == r) return tr[u].sum;
int mid = tr[u].l + tr[u].r >> 1;
if (mid < l) return query(rs, l, r);
if (r <= mid) return query(ls, l, r);
return query(ls, l, mid) + query(rs, mid + 1, r);
}
LL ans;
int main() {
cin >> n;
// 一般使用线段树,都喜欢从下标1开始,所以我们在处理数据时尽量让下标从1开始
// b[]在下面的代码中有两个阶段的含义,目前的含义是a[i]的备份,因为一会a[]由小到大排序后,原始数组就丢失了位置信息就不见了,需要b备份出来
for (int i = 1; i <= n; i++) cin >> a[i], b[i] = a[i];
// ① 原始数组由小到大排序
sort(a + 1, a + n + 1);
// ② 离散化,遍历原始数组b[i],找出它的排名,即b[2]=3表示第2个输入的是排名第3的
for (int i = 1; i <= n; i++) b[i] = lower_bound(a + 1, a + n + 1, b[i]) - a;
// 构建线段树
build(1, 1, n); // n个数字
// 边查边添加
for (int i = 1; i <= n; i++) {
modify(1, b[i], 1); // 占据b[i]这个位置
ans += query(1, b[i] + 1, n); // 出现的比我早,号还比我大的有多少个
}
// 输出
printf("%lld\n", ans);
return 0;
}

View File

@@ -0,0 +1,31 @@
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int a[N], al;
int b[N], bl;
void mul(int a[], int &al, int b[], int &bl) {
int t = 0;
int c[N] = {0};
for (int i = 0; i < al; i++)
for (int j = 0; j < bl; j++)
c[i + j] += a[i] * b[j];
al += bl;
for (int i = 0; i < al; i++) {
t += c[i];
a[i] = t % 10;
t /= 10;
}
while (al > 0 && a[al] == 0) al--;
}
int main() {
string x, y;
cin >> x >> y;
for (int i = x.size() - 1; i >= 0; i--) a[al++] = x[i] - '0';
for (int i = y.size() - 1; i >= 0; i--) b[bl++] = y[i] - '0';
mul(a, al, b, bl);
for (int i = al; i >= 0; i--) printf("%d", a[i]);
return 0;
}

View File

@@ -0,0 +1,28 @@
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5+10;
int a[N], b[N];
int al, bl;
void mul(int a[], int &al, int b[], int &bl) {
int t = 0;
int c[N] = {0};
for (int i = 0; i < al; i++)
for (int j = 0; j < bl; j++)
c[i + j] += a[i] * b[j];
al += bl;
for (int i = 0; i < al; i++) {
t += c[i];
a[i] = t % 10;
t /= 10;
}
while (al > 0 && a[al] == 0) al--;
}
int main() {
string x, y;
cin >> x >> y;
for (int i = x.size()-1; i >= 0; i--) a[al++] = x[i] - '0';
for (int i = y.size()-1; i >= 0; i--) b[bl++] = y[i] - '0';
mul(a, al, b, bl);
for (int i = al; i >= 0; i--) cout << a[i];
return 0;
}