算法 {CPP在算法中的应用}
@LOC: 2
namespace和class的不同优劣点
场景1: 你需要一个很大的数组 int A[ 1003][ 1003]
:
@IF(class): 这很不好处理 因为class obj;
这个对象是在栈空间里的 这容易爆栈!
.
解决办法: 你需要写成int (* A)[ 1003]
然后动态开内存 可是 你又需要多存储很多的指针 这种办法不好);
@IF(namespace) 他一定是在全局堆空间 这很好;
场景2: 必须要强行进行一个初始化操作(比如数位DP 他在使用之前 必须先预处理出来DP
即必须进行初始化)
@IF(class): 这很方便 直接放到构造函数即可;
@IF(namespace): 不太方便 你必须牢记着 手动的去调用一个全局函数;
.
解决办法: 在初始化函数那 写上文字(请手动调用) 让程序一开始编译出错, 然后用户就可以知道了;
类对象的全局初始化
一般来说 不要在全局初始化类对象, 而是在函数里去初始化, 因此:
class Solution{
Graph * G;
// Graph G = Graph( 1e5);
Solution(){
G = new Graph( 1e5);
}
}
这是很正规的写法;
如果非要在全局初始化 也可以, 但你直接写成Graph G( 1e5);
这是错误的!(Error: expected parameter declarator
)
而是要写成Graph G = Graph( 1e5);
这是规定…
不指定个数的 多组数据录入
if( false == (bool)( cin>> N))
等价于 if( EOF != scanf("%d", &N))
;
用move
来使用vector
的移动构造函数
@MARK: @LOC_1
;
当前有一个vector< ST> A( 10)
(他里面有10个ST对象), 现在要把他赋值给一个另一个vector<ST> B;
即进行B = A;
操作;
如果你直接使用B = A
, 那么他会调用vector
的拷贝赋值运算符operator=( const vector &)
;
.
从本质讲, 一个vector 他里面有一个ST * arr
的动态申请的内存, 此时A里面的arr
和 B里面的arr
, 两者是没有任何关系的 也就是一共是有20
个ST大小的内存;
假如说, 你执行完B = A
操作后, 永远也不会再使用A
(这点非常重要), 换句话说, 你执行完B = A
后 可以想象成 你主动调用了~A()
把他给析构掉了;
那么这种情况, 你可以使用B = std::move( A)
来替代 通常的B = A
, 那么他会调用vector
的移动赋值运算符operator=( ST &&)
;
.
简单讲 他会执行: B.arr = A.arr, A.arr = nullptr
, 因此 不像深拷贝那样 (把A.arr
的每个元素 赋值给 B.arr
的每个元素), 而是浅拷贝 很简单 就把B
的指针 直接指向A的指针;
即, 拷贝赋值(包括拷贝构造) 他是深拷贝, 两个对象 只是数据相同 但他们的内存地址 是完全不同的;
而 移动赋值(包括移动构造) 他是浅拷贝 , 两个对象 他们的内存地址 是指向同一地址的;
tuple,vector
的排序规则
和pair
一样, 先比较<0>
, 如果相同则 然后比较<1>
, 以此类推;
cout
输出浮点数 (precision, setf( ios::fixed)
)
执行一次操作: cout.setf( ios::fixed), cout.precision( 3);
以后调用cout<< 123.45555
会输出123.456
;
@DELI;
cin.precision( 3)
没有作用, 他并不说 控制录入的double
就3位精度 不是的, 好像他什么作用也没有…
他不会影响cout.precision()
所设置的 输出的精度;
__builtin_popcount
int __builtin_popcount( unsigned int)
注意你只能放int
类型, 不可以是long long
;
而且, 他实际上是获取 这块内存里 1的个数, 他并不关注符号 他关注的是二进制编码; 比如 -1
他的二进制编码为111...11
, 所以他的结果为32
;
使用static
实现DFS的记忆化
MARK: @LOC_0
;
T Dfs( int _cur){
static bool __is_first = true;
static T __record[ 100005];
if( __is_first){
__is_first = false;
memset( __record, -1, sizeof( __record));
}
if( __record[ _cur] != -1){ return __record[ _cur];}
return __record[ cur] = ?;
}
@DELI;
请注意, 如果是LeetCode
以上是错误的, 因为力扣的多组数据 只是入口函数的多次调用而已, 因此, 在力扣 你需要将__is_first
修改到全局变量 然后在入口函数将他置为true
, 如下示例:
bool __is_first;
T Dfs( int _cur){
static T __record[ 100005];
if( __is_first){
__is_first = false;
memset( __record, -1, sizeof( __record));
}
if( __record[ _cur] != -1){ return __record[ _cur];}
return __record[ cur] = ?;
}
? `力扣的入口函数`(){
__is_first = true;
...
}
fill
, iota
fill( A.begin(), A.end(), K)
将容器的每个元素赋值为K;
iota( A.begin(), A.end(), K)
将容器的每个元素赋值为K, K+1, K+2, ...
(内部是通过不断执行++K
来赋值的);
.
iota 不是缩写, 他本身就是个单词 (表示一个极小的量);
sqrt
获取下取整的平方根
long long c = sqrt( a)
可以获取 任何>=0
的long long a
的 平方根;
且满足: c*c <= a && (c+1)*(c+1)>a
;
手写数组 来替代 容器queue/stack
T arr[ ?]; int head = 0, tail = 0;
可以替代queue
;
T arr[ ?]; int tail = 0;
可以替代stack
;
手写的数组, 比系统的容器 效率要高;
全局数组 与 vector
的效率
int A[ 1003]; // 方式1
int main(){
vector< int> B(n); // 方式2
//--
...
}
使用全局数组(即方式1), 要比 方式2的vector
, 快一倍; 因此要尽量避免使用vector
;
ios::sync_with_stdio( false), cin.tie( NULL)
的使用须知
此时, 要么所以的输入 都用cin
, 要么都用scanf
, 不可以两个都混用;
输出也是, 要么都用cout
要么都用printf
, 不可以两个都混用;
找到数组中所有 X X X元素的位置
当然你可以使用个
O
(
n
)
O(n)
O(n)的for循环来实现, 这里使用系统的函数find
**数组**
constexpr int N = 6;
int A[ N] = { 1, 2, 3, 1, 2, 3};
int X = 3;
for( auto it = find( A, A + N, X); it != A + N; it = find( it + 1, A + N, X)){
}
@Delimiter
**vector**
vector< int> A{ 1, 2, 3, 1, 2, 3};
int X = 3;
for( auto it = find( A.begin(), A.end(), X); it != A.end(); it = find( it + 1, A.end(), X)){
}
删除set
中的一个连续子段
删除set
中所有处在[L, R]
区间内的元素;
.
比如set{1, 3, 5, 7, 9}
, L = 4, R = 8
, 则你需要将5,7
删除掉;
(可以每次调用lower_bound(L)
来删除, 但时间复杂度是O(k * log(n))
其中k是要删除的元素个数; 我们有更优的做法)
因为set
是有序的 我们将其看成是一个数列, 那么要删除的元素 会是该数列中的一个连续子段;
.
比如
[
a
,
b
,
X
,
Y
,
Z
,
c
,
d
]
[a, b, X, Y, Z, c, d]
[a,b,X,Y,Z,c,d] 其中
[
X
,
Y
,
Z
]
[X,Y,Z]
[X,Y,Z]就是要删除的连续子段;
.
使用iter = lower_bound( L)
可以定位到
X
X
X;
.
此时用到一个操作 iter = set.erase( iter)
, 他会删除
X
X
X 且将其下一位指针作为返回值 即将
Y
Y
Y返回;
.
.
(注意erase(值)
和 erase(迭代器)
是不同的, 他俩的返回值也不同);
for( auto iter = S.lower_bound( l); iter != S.end() && *iter <= r; iter = S.erase( iter)){
}
对拍程序
compare.cpp
ASSERT_( system("generate_data.exe > data.in") == 0);
ASSERT_( system("supimo.exe < data.in > supimo.out") == 0);
ASSERT_( system("correct.exe < data.in > correct.out") == 0);
ASSERT_( system("fc supimo.out correct.out") == 0);
这个对拍程序, 一次生成后 得到compare.exe
, 以后就再不需要动了;
因为以后修改的, 就是generate_data, supimo, correct.cpp
这三个文件, 只要这三个程序.exe
更新了, 就直接调用compare.exe
即可;
system()
函数是调用命令行, 相当于你在命令行的执行; 当执行没有问题 返回0
;
fc
是一个命令行里的语法 用于判定两个文件是否相同;
数组元素去重
数组
int A[N];
sort( A, A + N); // 或从大到小排序, 只要单调即可;
N = unique( A, A + N) - A;
//>< 此时, N已经改变, A[0,1,...,N)为N个不同的元素 也是单调的 (若之前A是升序 则现在前N个元素也是升序);
--
vector
vector< int> A(N);
sort( A.begin(), A.end()); // 或从大到小排序, 只要单调即可;
A.erase( unique( A.begin(), A.end()), A.end());
//>< A里面为不同的元素 也是单调的 (若之前A是升序 则现在前N个元素也是升序);
assert
的报警提示
For assert( succ)
, when succ = false
the assert would be happened;
If we wanna add some hints to that, we can use assert( succ && "some hints")
;
++
自加/自减
连续使用
++ ++ a;
is valid, for example, s.erase( ++ ++ it)
means deleting the Second-Element that behind it
;
@Delimiter
可应用于临时对象
s.erase( ++ it.begin());
which would produce a new Temporary-Iterator it
that behinds it.begin()
;
set,map
的迭代器
The iterator of set, multiset, map, multimap
are Bidirectional (support only ++
or --
)
s.erase( ++ s.begin());
: delete the Second-Element;
反向迭代器转化为一般迭代器
The parameter of erase
must be iterator
not reverse_iterator
, so if we wanna delete a Reverse-Iterator (e.g., r_it = rbegin()
)), the operation is s.erase( (++ r_it).base())
;
cerr
调试输出,各算法平台的调试机制
cerr << 12; cout<< 34;
In our IDE-environment: both 12, 34
would be outputted, visually, no distinction between them;
In Online-Judge (except LeetCode), 12
would not affect the answer (only cout
would be viewed as answer)
.
For example, in AcWing, all cout
would be showed if you have no cerr
; otherwise, only cerr
would be showed and no cout
is showed once you have cerr
;
In LeetCode, all cerr
are forbidden, only cout
would be showed (and also cout
would not affect the answer which is only specified by the return-variable of function)
整型最大最小值INT32_MAX
INT32_MAX
, INT32_MIN
; UINT32_MAX
INT64_MAX
, INT64_MIN
; UINT64_MAX
使用自定义类型来替代tuple, pair
struct ST{
T1 t1;
T2 t2;
T3 t3;
};
is always better than tuple< T1, T2, T3>
and pair< T1, pair< T2, T3>>
next_permutation
vector< int> Perm{ 0, 1, 2, 3};
do{
Perm is `0123` `0132` `0213` `0231` `0312` `0321` `1023` ...
}while( next_permutation( Perm.begin(), Perm.end()));
Make sure the initial value of Perm
must be {0, 1, 2, 3, ...}
.
stringstream
stringstream
will get all sub-strings divided by a sequence of space(
)
s = " 1 2 3 ";
stringstream ss( s);
string a;
while( ss >> a){
a = "1", "2", "3";
}
if all sub-string are int
, string a
can be written to int a
;
避免使用匿名函数
It is always better to using a Normal-Function than a Anonymous-Function;