Chapter 1 基础算法
知识
模板
1. 快速排序
C++ 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42 #include <iostream>
using namespace std ;
const int N = 100010 ;
int q [ N ];
void quick_sort ( int q [], int l , int r )
{
if ( l >= r ) return ; // 边界条件
// 初始化初值, 尤其注重边界 (+1 / -1)
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 ]);
}
// 解决子问题
quick_sort ( q , l , j );
quick_sort ( q , j + 1 , r );
}
int main ()
{
int n ;
cin >> n ;
for ( int i = 0 ; i < n ; i ++ ) cin >> q [ i ];
quick_sort ( q , 0 , n - 1 );
for ( int i = 0 ; i < n -1 ; i ++ ) cout << q [ i ] << " " ;
cout << q [ n -1 ] << endl ;
return 0 ;
}
2. 归并排序
C++ 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43 #include <iostream>
using namespace std ;
const int N = 1e5 + 10 ;
int a [ N ], tmp [ 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 );
// 再对两个子段进行cmp (tmp存的是"自小到大"序)
int k = 0 , i = l , j = mid + 1 ;
while ( i <= mid && j <= r )
if ( q [ i ] <= q [ j ]) tmp [ k ++ ] = q [ i ++ ];
else tmp [ k ++ ] = q [ j ++ ];
// 尾巴 (其中一个已经遍历完了, 另一个直接尾加即可)
while ( i <= mid ) tmp [ k ++ ] = q [ i ++ ];
while ( j <= r ) tmp [ k ++ ] = q [ j ++ ];
for ( i = l , j = 0 ; i <= r ; i ++ , j ++ ) q [ i ] = tmp [ j ];
}
int main ()
{
int n ;
cin >> n ;
for ( int i = 0 ; i < n ; i ++ ) cin >> a [ i ];
merge_sort ( a , 0 , n - 1 );
for ( int i = 0 ; i < n -1 ; i ++ ) cout << a [ i ] << " " ;
cout << a [ n -1 ] << endl ;
return 0 ;
}
求逆序对 (数量):
C++ 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48 // 完全基于merge_sort()的模板
#include <iostream>
using namespace std ;
typedef long long LL ;
const int N = 1e5 + 10 ;
int a [ N ], tmp [ N ];
LL merge_sort ( int q [], int l , int r )
{
if ( l >= r ) return 0 ;
int mid = l + r >> 1 ;
// 先处理子问题, 注意这里的result是两个子问题的"相加"
LL res = merge_sort ( q , l , mid ) + merge_sort ( q , mid + 1 , r );
int k = 0 , i = l , j = mid + 1 ;
while ( i <= mid && j <= r )
if ( q [ i ] <= q [ j ]) tmp [ k ++ ] = q [ i ++ ];
else
{
// |l---(i)---mid|mid+1----(j)--r|
// 既然(i,j)构成了逆序对, 那么(i+1, ..., mid)也都构成
res += mid - i + 1 ;
tmp [ k ++ ] = q [ j ++ ];
}
while ( i <= mid ) tmp [ k ++ ] = q [ i ++ ];
while ( j <= r ) tmp [ k ++ ] = q [ j ++ ];
for ( i = l , j = 0 ; i <= r ; i ++ , j ++ ) q [ i ] = tmp [ j ];
return res ;
}
int main ()
{
int n ;
cin >> n ;
for ( int i = 0 ; i < n ; i ++ ) cin >> a [ i ];
cout << merge_sort ( a , 0 , n - 1 ) << endl ;
return 0 ;
}
3. 二分
(1) 整数二分:
给定一个按照升序排列的长度为 \(n\) 的整数数组,以及 \(q\) 个查询
对于每个查询,返回一个元素 \(k\) 的起始位置和终止位置(位置从 0 开始计数)
如果数组中不存在该元素,则返回 -1 -1
C++ 1
2
3
4
5
6
7
8
9
10
11
12 // 边界问题要注意, 有两套模板, 针对的是不同的寻找区间
// |-------red-------||-------green-------|
// (1) 查找red的右边界: mid = (l+r+1) >> 1
mid = ( l + r + 1 ) >> 1 ;
l = mid ;
r = mid -1 ;
// (2) 查找green的左边界: mid = (l+r) >> 1
mid = ( l + r ) >> 1 ;
r = mid ;
l = mid + 1 ;
代码模板:
C++ 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57 #include <iostream>
using namespace std ;
const int N = 100010 ;
int n , m , x ;
int q [ N ];
bool check_rhs ( int mid )
{
return q [ mid ] >= x ;
}
bool check_lfs ( int mid )
{
return q [ mid ] <= x ;
}
int main ()
{
cin >> n >> m ;
for ( int i = 0 ; i < n ; i ++ ) cin >> q [ i ];
while ( m -- ) // 进行m次查询
{
cin >> x ;
// 寻找x的右边界: "green"
int l = 0 , r = n - 1 ;
while ( l < r )
{
int mid = ( l + r ) >> 1 ;
if ( check_rhs ( mid )) r = mid ;
else l = mid + 1 ;
}
if ( q [ l ] != x ) cout << "-1 -1" << endl ;
else
{
cout << l << ' ' ; // ">=x"的min, 即: x的右边界
// 寻找x的左边界: "red"
int l = 0 , r = n - 1 ;
while ( l < r )
{
int mid = ( l + r + 1 ) >> 1 ;
if ( check_lfs ( mid )) l = mid ;
else r = mid - 1 ;
}
cout << l << endl ; // "<=x"的max, 即: x的左边界
}
}
return 0 ;
}
(2) 实数二分:
给定一个浮点数 \(n\) ,求它的三次方根:
C++ 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 // 不存在边界+1/-1问题, 但是要注意 "小数后几位" 这个写法
#include <iostream>
#include <iomanip>
using namespace std ;
int main ()
{
double x ;
cin >> x ;
double l = -100 , r = 100 ;
while ( r - l > 1e-8 ) // double判等的写法 (依据题目要求的输出精度, 一般多2)
{
double mid = ( l + r ) / 2 ;
if ( mid * mid * mid >= x ) r = mid ;
else l = mid ;
}
// 比如, 题目要答案精确到1e-6, 判等的条件就是1e-8.
cout << fixed << setprecision ( 6 ) << l << endl ;
return 0 ;
}
[!tip] 确定小数输出的精确位数
头文件: #include<iomanip>
写法是: cout << fixed << setprecision(6) << xxx << endl;
4. 前缀和 (1D + 2D)
C++ 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30 #include <iostream>
using namespace std ;
const int N = 100010 ;
int n , m ;
int a [ N ], s [ N ];
/*
求 [l, r] 的区间和: s[r] - s[l-1]
*/
int main ()
{
cin >> n >> m ;
for ( int i = 1 ; i <= n ; i ++ ) cin >> a [ i ];
// 1D前缀和的初始化
for ( int i = 1 ; i <= n ; i ++ ) s [ i ] = s [ i - 1 ] + a [ i ];
while ( m -- )
{
int l , r ;
cin >> l >> r ;
cout << s [ r ] - s [ l - 1 ] << endl ; // 1D区间和的计算
}
return 0 ;
}
C++ 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38 #include <iostream>
using namespace std ;
const int N = 1010 ;
int n , m , q ;
int s [ N ][ N ];
int main ()
{
/*
// 加速:
ios::sync_with_stdio(false);
cin.tie(0);
*/
cin >> n >> m >> q ;
// 2D前缀和的初始化-1
for ( int i = 1 ; i <= n ; i ++ )
for ( int j = 1 ; j <= m ; j ++ )
cin >> s [ i ][ j ]; // 本质: 每个点的2D前缀和数组先初始化成自身输入
// 2D前缀和的初始化-2
for ( int i = 1 ; i <= n ; i ++ )
for ( int j = 1 ; j <= m ; j ++ )
s [ i ][ j ] += s [ i - 1 ][ j ] + s [ i ][ j - 1 ] - s [ i - 1 ][ j - 1 ];
while ( q -- )
{
int x1 , y1 , x2 , y2 ;
cin >> x1 >> y1 >> x2 >> y2 ;
cout << s [ x2 ][ y2 ] - s [ x1 - 1 ][ y2 ] - s [ x2 ][ y1 - 1 ] + s [ x1 - 1 ][ y1 - 1 ] << endl ; // 2D区间和的计算
}
return 0 ;
}
[!tip] 加速IO
std::ios::sync_with_stdio(false);
std::cin.tie(0);
5. 差分 (1D + 2D)
C++ 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39 #include <iostream>
using namespace std ;
const int N = 100010 ;
int n , m ;
int a [ N ], b [ N ]; // a: 原数组 b: 对应的差分数组
void insert ( int l , int r , int c )
{
// 操作目的: a[l~r] += c
b [ l ] += c ;
b [ r + 1 ] -= c ;
}
int main ()
{
scanf ( "%d%d" , & n , & m );
for ( int i = 1 ; i <= n ; i ++ ) cin >> a [ i ];
// 差分数组初始化
for ( int i = 1 ; i <= n ; i ++ ) insert ( i , i , a [ i ]);
// 具体insert操作
while ( m -- )
{
int l , r , c ;
cin >> l >> r >> c ;
insert ( l , r , c );
}
// 对 差分数组b 求前缀和 <=> 原数组a
for ( int i = 1 ; i <= n ; i ++ ) b [ i ] += b [ i - 1 ];
for ( int i = 1 ; i <= n ; i ++ ) printf ( "%d " , b [ i ]);
return 0 ;
}
C++ 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51 #include <iostream>
using namespace std ;
const int N = 1010 ;
int n , m , q ;
int a [ N ][ N ], b [ N ][ N ];
void insert ( int x1 , int y1 , int x2 , int y2 , int c )
{
b [ x1 ][ y1 ] += c ;
b [ x2 + 1 ][ y1 ] -= c ;
b [ x1 ][ y2 + 1 ] -= c ;
b [ x2 + 1 ][ y2 + 1 ] += c ;
}
int main ()
{
cin >> n >> m >> q ;
for ( int i = 1 ; i <= n ; i ++ )
for ( int j = 1 ; j <= m ; j ++ )
cin >> a [ i ][ j ];
// 二维差分数组 初始化
for ( int i = 1 ; i <= n ; i ++ )
for ( int j = 1 ; j <= m ; j ++ )
insert ( i , j , i , j , a [ i ][ j ]);
// 具体insert操作
while ( q -- )
{
int x1 , y1 , x2 , y2 , c ;
cin >> x1 >> y1 >> x2 >> y2 >> c ;
insert ( x1 , y1 , x2 , y2 , c );
}
// 对 差分数组b 求前缀和 <=> 原数组a
for ( int i = 1 ; i <= n ; i ++ )
for ( int j = 1 ; j <= m ; j ++ )
b [ i ][ j ] += b [ i - 1 ][ j ] + b [ i ][ j - 1 ] - b [ i - 1 ][ j - 1 ];
for ( int i = 1 ; i <= n ; i ++ )
{
for ( int j = 1 ; j <= m ; j ++ ) cout << b [ i ][ j ] << " " ;
cout << endl ;
}
return 0 ;
}
6. 双指针
左右指针:两个指针,相向而走,中间相遇
快慢指针:两个指针,有快有慢,同向而行
(1) 数组元素的目标和: a[]
和 b[]
各自升序, 求 a[i] + b[j] == x
的 (i, j)
元组数
左右指针
C++ 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 #include <iostream>
using namespace std ;
const int N = 1e5 + 10 ;
int n , m , x ;
int a [ N ], b [ N ];
int main ()
{
cin >> n >> m >> x ;
for ( int i = 0 ; i < n ; i ++ ) cin >> a [ i ];
for ( int i = 0 ; i < m ; i ++ ) cin >> b [ i ];
// 相较于O(n^2)的遍历, 这种的优势是: j 一旦不合理就直接结束, 复杂度大幅降低
for ( int i = 0 , j = m - 1 ; i < n ; i ++ )
{
while ( j >= 0 && a [ i ] + b [ j ] > x ) j -- ;
if ( j >= 0 && a [ i ] + b [ j ] == x ) cout << i << ' ' << j << endl ;
} // 必须要 j>=0 !!!
return 0 ;
}
(2) 判断子序列: 如序列 {\(a1\) ,\(a3\) ,\(a5\) } 是序列 {\(a1\) ,\(a2\) ,\(a3\) ,\(a4\) ,\(a5\) } 的一个子序列
快慢指针
C++ 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 // 整个过程中j指针不断扫描b数组并且向后移动,相当于不断给i指针所指向的a数组创建匹配的机会,只有匹配成功时i指针才会向后移动一位,当i == n时,说明全部匹配成功
#include <iostream>
#include <cstdio>
using namespace std ;
const int N = 1e5 + 10 ;
int a [ N ], b [ N ];
int main ()
{
int n , m ;
cin >> n >> m ;
for ( int i = 0 ; i < n ; i ++ ) cin >> a [ i ];
for ( int i = 0 ; i < m ; i ++ ) cin >> b [ i ];
int i = 0 ;
for ( int j = 0 ; j < m ; j ++ ) // j -> b[]
{
if ( i < n && a [ i ] == b [ j ]) i ++ ;
} // 必须要 i<n !!!
if ( i == n ) puts ( "Yes" );
else puts ( "No" );
return 0 ;
}
7. 位运算
(1) 求一个数的二进制中1的个数: 基于lowbit(x)
C++ int lowbit ( int x ) { // def: x 的二进制表示中, 最后一个1 + 全0 所构成的整数
// x = 1100
// lowbit(x) = 100
return x & ( - x );
}
for ( int i = x ; i ; i -= lowbit ( i )) num ++ ;
(2) 后续的 "快速幂" 之类的...
C++ 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29 // 一个数乘(除) 2 的非负整数次幂
int mulPowerOfTwo ( int n , int m ) { // 计算 n*(2^m)
return n << m ;
}
int divPowerOfTwo ( int n , int m ) { // 计算 n/(2^m)
return n >> m ;
}
// 判断两非零数符号是否相同
bool isSameSign ( int x , int y ) { // 有 0 的情况例外
return ( x ^ y ) >= 0 ;
}
// 操作一个数的二进制位
// (1) 获取 a 的第 b 位. 最低位编号为 0
int getBit ( int a , int b ) {
return ( a >> b ) & 1 ;
}
// (2) 将 a 的第 b 位设置为 1. 最低位编号为 0
int setBit ( int a , int b ) { return a | ( 1 << b ); }
// (3) 将 a 的第 b 位设置为 0 ,最低位编号为 0
int unsetBit ( int a , int b ) { return a & ~ ( 1 << b ); }
// (4) 将 a 的第 b 位取反. 最低位编号为 0
int flapBit ( int a , int b ) { return a ^ ( 1 << b ); }
8. 区间合并
纯模板: 先按左端点排序,再维护一个区间,与 “后来者区间” 进行三种情况的比较 (尾部)
C++ 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47 #include <iostream>
#include <vector>
#include <algorithm>
using namespace std ;
typedef pair < int , int > PII ; // 默认是按照左端点排序
void merge ( vector < PII > & segs )
{
vector < PII > res ;
sort ( segs . begin (), segs . end ());
int st = -2e9 , ed = -2e9 ; // 初始化
for ( auto seg : segs )
if ( ed < seg . first ) // 需要另立门户
{
if ( st != -2e9 ) res . push_back ({ st , ed });
st = seg . first , ed = seg . second ;
}
else ed = max ( ed , seg . second ); // 直接尾加
if ( st != -2e9 ) res . push_back ({ st , ed });
segs = res ;
}
int main ()
{
int n ;
cin >> n ;
vector < PII > segs ;
for ( int i = 0 ; i < n ; i ++ )
{
int l , r ;
cin >> l >> r ;
segs . push_back ({ l , r });
}
merge ( segs );
cout << segs . size () << endl ;
return 0 ;
}
9. 高精度算法
(1) 高精度加法
做法:
Text Only 1. 用 a, b 两个字符串存储输入。a = 567, b = 28
2. 为了方便计算,将两个数分别 倒序 存放在 A, B 两个整数数组中。 A = [7, 6, 5], B = [8, 2]
3. 新建整数数组 C 保存结果,整型变量 t 保存进位,初始 t = 0
4. 将各个位上的数字相加,求出结果对应位上的数字和进位
例如对个位计算: A[0] + B[0] = 7 + 8 = 15, 结果个位上是 5, 进位是 1. 所以 C[0] = 5, 进位 t = 1
5. 最后把结果数组 C 中就保存了计算倒序结果,倒序输出就是答案
代码:
C++ 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36 #include <iostream>
#include <vector>
using namespace std ;
vector < int > add ( vector < int > & A , vector < int > & B )
{
//为了方便计算,让A中保存较长的数字, B中保存较短的数字
if ( A . size () < B . size ()) return add ( B , A );
//保存结果的数组
vector < int > C ;
//进位,开始时是0
int t = 0 ;
//依次计算每一位
for ( int i = 0 ; i < A . size (); i ++ )
{
t += A [ i ]; //加上 A 的第 i 位上的数字
if ( i < B . size ()) t += B [ i ]; //加上 B 的第 i 位上的数字
C . push_back ( t % 10 ); //C 中放入结果
t /= 10 ; //t 更新成进位
}
//最后如果进位上有数,放进结果数组
if ( t ) C . push_back ( t );
return C ; //返回结果
}
int main ()
{
string a , b ; //以字符串形式保存输入的两个整数
vector < int > A , B ; //保存两个整数的数组
cin >> a >> b ; //接收输入
for ( int i = a . size () - 1 ; i >= 0 ; i -- ) A . push_back ( a [ i ] - '0' ); //倒序存储第一个数
for ( int i = b . size () - 1 ; i >= 0 ; i -- ) B . push_back ( b [ i ] - '0' ); //倒序存储第二个数
auto C = add ( A , B ); //调用加和函数
for ( int i = C . size () - 1 ; i >= 0 ; i -- ) cout << C [ i ]; //倒序输出C中的数字
cout << endl ;
return 0 ;
}
python 秒了:
Python a = input ()
b = input ()
print ( a + b )
(2) 高精度减法
C++ 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60 #include <iostream>
#include <vector>
using namespace std ;
bool cmp ( vector < int >& A , vector < int > & B )
{
if ( A . size () != B . size ()) return A . size () > B . size (); // 直接return 了就不用else
for ( int i = A . size (); i >= 0 ; i -- )
if ( A [ i ] != B [ i ])
return A [ i ] > B [ i ];
return true ;
}
vector < int > sub ( vector < int >& A , vector < int > & B )
{
vector < int > C ;
int t = 0 ;
for ( int i = 0 ; i < A . size (); i ++ )
{
t = A [ i ] - t ;
if ( i < B . size ()) t -= B [ i ];
C . push_back (( t + 10 ) % 10 ); // 合而为1
if ( t < 0 ) t = 1 ;
else t = 0 ;
}
while ( C . size () > 1 && C . back () == 0 ) C . pop_back (); // 去掉前导0
return C ;
}
int main ()
{
string a , b ;
vector < int > A , B ;
cin >> a >> b ;
for ( int i = a . size () - 1 ; i >= 0 ; i -- ) A . push_back ( a [ i ] - '0' );
for ( int i = b . size () - 1 ; i >= 0 ; i -- ) B . push_back ( b [ i ] - '0' );
if ( cmp ( A , B ))
{
auto C = sub ( A , B );
for ( int i = C . size () - 1 ; i >= 0 ; i -- ) printf ( "%d" , C [ i ]);
return 0 ;
}
else
{
auto C = sub ( B , A );
printf ( "-" );
for ( int i = C . size () - 1 ; i >= 0 ; i -- ) printf ( "%d" , C [ i ]);
return 0 ;
}
return 0 ;
}
python 秒了:
Python a = input ()
b = input ()
print ( a - b )
(3) 高精度乘法
C++ 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63 #include <iostream>
#include <vector>
using namespace std ;
vector < int > mul ( vector < int > A , int b )
{
vector < int > C ;
int t = 0 ; // 进位
for ( int i = 0 ; i < A . size (); i ++ ){
t = t + A [ i ] * b ;
C . push_back ( t % 10 );
t = t / 10 ;
}
// 最后一次进位的结果还没有存储结束
while ( t )
{
C . push_back ( t % 10 );
t = t / 10 ;
}
// 将高位的0一一弹出
while ( C . size () > 1 && C . back () == 0 )
{
// vetcor数组的末端存储数字的高位
C . pop_back ();
}
return C ;
}
int main ()
{
string a ;
int b ;
cin >> a ;
scanf ( "%d" , & b );
vector < int > A ;
for ( int i = a . size () - 1 ; i >= 0 ; i -- )
{
A . push_back ( a [ i ] - '0' );
}
auto C = mul ( A , b );
// if(C[C.size() - 1] == 0)
// {
// // 最高位是0 说明全部都是0
// cout<<0;
// return 0;
// }
for ( int i = C . size () - 1 ; i >= 0 ; i -- )
{
printf ( "%d" , C [ i ]);
}
return 0 ;
}
python 秒了:
Python a = input ()
b = input ()
print ( a * b )
(4) 高精度除法
C++ 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35 #include <iostream>
#include <vector>
#include <algorithm>
using namespace std ;
//int r=0;
vector < int > div ( vector < int > & A , int B , int & r )
{
// r传入r的地址,便于直接对余数r进行修改
vector < int > C ;
for ( int i = 0 ; i < A . size (); i ++ ){ //对A从最高位开始处理
r = r * 10 + A [ i ]; //将上次的余数*10在加上当前位的数字,便是该位需要除的被除数
C . push_back ( r / B ); //所得即为商在这一位的数字
r = r % B ;
}
// 由于在除法运算中,高位到低位运算,因此C的前导零都在vector的前面而不是尾部,vector只有删除最后一个数字pop_back是常数复杂度,而对于删除第一位没有相应的库函数可以使用,而且删除第一位,其余位也要前移,
// 因此我们将C翻转,这样0就位于数组尾部,可以使用pop函数删除前导0
reverse ( C . begin (), C . end ());
while ( C . size () > 1 && C . back () == 0 ) C . pop_back ();
return C ;
}
int main ()
{
string a ;
int B , r = 0 ; //代表余数
cin >> a >> B ;
vector < int > A ;
for ( int i = 0 ; i < a . size (); i ++ ) A . push_back ( a [ i ] - '0' ); //注意这次的A是由高为传输至低位,由于在除法的手算过程中,发现从高位进行处理
auto C = div ( A , B , r );
for ( int i = C . size () -1 ; i >= 0 ; i -- ) cout << C [ i ]; //将C从最高位传给最低位
cout << endl << r ; //输出余数
cout << endl ;
return 0 ;
}
python 秒了:
Python a = int ( input ())
b = int ( input ())
print ( a // b )
print ( a % b )
[!tip] python 基本的 I/O
input()
and print()
Python >>> a = [ 1 , 2 , 3 ]; print ( a [ - 1 ]) # 打印时默认末尾换行
3
>>> print ( ans [ 0 ], ans [ 1 ]) # 可以输出任意多个变量,默认以空格间隔
1 2
>>> print ( a [ 0 ], a [ 1 ], end = '' ) # 令 end='', 使末尾不换行
1 2 >>>
>>> print ( a [ 0 ], a [ 1 ], sep = ', ' ) # 令 sep=', ',改变间隔样式
1 , 2
>>> print ( str ( a [ 0 ]) + ', ' + str ( a [ 1 ])) # 输出同上,但是手动拼接成一整个字符串
Python >>> s = input ( '请输入一串数字: ' ); s # 自己调试时可以向 input() 传入字符串作为提示
请输入一串数字 : 1 2 3 4 5 6
'1 2 3 4 5 6'
10. 离散化
当有些数据因为本身很大或者类型不支持,自身无法作为数组的下标来方便地处理,而影响最终结果的只有元素之间的相对大小关系时,我们可以 将原来的数据按照排名来处理 问题,即离散化
[!tip] 离散化常见使用场景
当原始数据的值域非常大(例如 109)或者包含非整数,但我们只关心这些数据之间的相对大小关系 时,就可以用离散化将这些庞大的值映射到 [1, n]
这个紧凑的整数范围内
类别1: 相同数值-映射到相同点
通常原数组中会有重复的元素,一般把相同的元素离散化为相同的数据
创建原数组的副本
将副本中的值从小到大排序
将排序好的副本去重
查找 原数组的每一个元素在副本中的位置,位置即为排名 ,将其作为离散化后的值
[!tip] TLDR 相同数值-映射到相同点
排序 --> 去重 --> 位置更新
模板写法 1: 普通数组
C++ // arr[i] 为初始数组,下标范围为 [1, n]
for ( int i = 1 ; i <= n ; ++ i ) // step 1
tmp [ i ] = arr [ i ];
std :: sort ( tmp + 1 , tmp + n + 1 ); // step 2
int len = std :: unique ( tmp + 1 , tmp + n + 1 ) - ( tmp + 1 ); // step 3
for ( int i = 1 ; i <= n ; ++ i ) // step 4
arr [ i ] = std :: lower_bound ( tmp + 1 , tmp + len + 1 , arr [ i ]) - tmp ;
正确性体现:
Text Only 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41 init() ->
- arr = {105, 20, 777, 20, 99}
- tmp = {105, 20, 777, 20, 99}
==================================================================
std::sort(tmp + 1, tmp + n + 1) ->
tmp = {20, 20, 99, 105, 777}
==================================================================
std::unique(tmp + 1, tmp + n + 1) - (tmp + 1); ->
得到去重后的 tmp 长度:
1. 它会把所有不重复的元素移动到数组的 最前面
2. 它会返回一个指向 "最后一个不重复元素" "之后位置" 的指针(迭代器)
std::unique 后, tmp 数组的内容变为 {20, 99, 105, 777, ?}
std::unique 返回的指针指向 777 的下一个位置,也就是 tmp[5] 的地址
- ?: tmp[5]
- 20: tmp[1]
std::unique(...) - (tmp + 1) 指针减法, 得到4, 即为 tmp去重后 的区间长度len
==================================================================
lower_bound() 函数就像一个高效的图书管理员,它在一个 "排好序" 的书架上用 "二分法" 帮你找书
- tmp + 1: 查找区间的左端点
- tmp + 1 + len: 查找区间的右端点 (告诉管理员找到 tmp 数组的 第4个元素 之后 就停止)
完成任务后, 它返回的 不是数字3, 而是一个 指向 tmp数组中105这个元素存储位置 的 "指针"
==================================================================
finally, we have this:
{105, 20, 777, 20, 99} 被成功地离散化为:
{3, 1, 4, 1, 2}
模板写法 2: vector
C++ // std::vector<int> arr;
std :: vector < int > tmp ( arr ); // tmp 是 arr 的一个副本
std :: sort ( tmp . begin (), tmp . end ());
tmp . erase ( std :: unique ( tmp . begin (), tmp . end ()), tmp . end ());
for ( int i = 0 ; i < n ; ++ i )
arr [ i ] = std :: lower_bound ( tmp . begin (), tmp . end (), arr [ i ]) - tmp . begin ();
正确性体现:
TL; DR
类别2: 相同数值-映射到不同点
有时候会把 相同的元素根据输入顺序 离散化为 不同的数据
结构体! 创建原数组的副本,同时记录每个元素出现的位置
将副本按值从小到大排序,当值相同时,按出现顺序从小到大排序
将离散化后的数字放回原数组
模板写法:
C++ 1
2
3
4
5
6
7
8
9
10
11
12
13
14 struct Data {
int idx ;
int val ;
} input [ maxn ];
bool cmp ( Data a , Data b ) {
if ( a . val == b . val ) return a . idx < b . idx ;
return a . val < b . val ;
}
for ( int i = 1 ; i <= n ; i ++ ) cin >> arr [ i ]; // 原数组
for ( int i = 1 ; i <= n ; i ++ ) input [ i ] = Data { i , arr [ i ]}; // idx捆绑
std :: sort ( input + 1 , input + 1 + n );
for ( int i = 1 ; i <= n ; i ++ ) arr [ input [ i ]. idx ] = i ;
正确性说明:
Text Only 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31 arr = {105, 20, 777, 20, 99}
tmp[1] = {idx: 1, val: 105}
tmp[2] = {idx: 2, val: 20}
tmp[3] = {idx: 3, val: 777}
tmp[4] = {idx: 4, val: 20}
tmp[5] = {idx: 5, val: 99}
==================================================================
after std::sort(input + 1, input + 1 + n):
tmp[1] = {idx: 2, val: 20}
tmp[2] = {idx: 4, val: 20}
tmp[3] = {idx: 5, val: 99}
tmp[4] = {idx: 1, val: 105}
tmp[5] = {idx: 3, val: 777}
==================================================================
after arr[ input[i].idx ] = i:
arr[2] = 1: 把排名 1 写回到 arr[2]
arr[4] = 2: 把排名 2 写回到 arr[4]
...
->
finally:
arr = {4, 1, 5, 2, 3}
例题:
C++ 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74 #include <iostream>
#include <vector>
#include <algorithm>
using namespace std ;
typedef pair < int , int > PII ;
const int N = 300010 ;
int n , m ;
int a [ N ], s [ N ];
vector < int > alls ;
vector < PII > add , query ;
int find ( int x ) // 查找大于等于x的最小的值的下标
{
int l = 0 , r = alls . size () - 1 ;
while ( l < r )
{
int mid = l + r >> 1 ;
if ( alls [ mid ] >= x ) r = mid ;
else l = mid + 1 ;
}
// 因为后续的 前缀和 和 查询 都采用了 1-based 的数组下标
return r + 1 ;
}
int main ()
{
cin >> n >> m ;
for ( int i = 0 ; i < n ; i ++ )
{
int x , c ;
cin >> x >> c ;
add . push_back ({ x , c }); // {num_idx, val}
alls . push_back ( x ); // num_idx
}
for ( int i = 0 ; i < m ; i ++ )
{
int l , r ;
cin >> l >> r ;
query . push_back ({ l , r }); // 查询区间
alls . push_back ( l ); // num_idx
alls . push_back ( r ); // num_idx
}
// 去重
sort ( alls . begin (), alls . end ());
alls . erase ( unique ( alls . begin (), alls . end ()), alls . end ());
// 处理插入
for ( auto item : add )
{
int x = find ( item . first );
a [ x ] += item . second ;
}
// 预处理前缀和
for ( int i = 1 ; i <= alls . size (); i ++ ) s [ i ] = s [ i - 1 ] + a [ i ];
// 处理询问
for ( auto item : query )
{
int l = find ( item . first ), r = find ( item . second );
cout << s [ r ] - s [ l - 1 ] << endl ;
}
return 0 ;
}