**1____**前言
2020/9/15开始制作此板子
,用于ACM比赛,节省记忆模板时间,便于过模板题,以及对高深的算法的记录
每日一遍,我真的好菜 时间复杂度(渐近时间复杂度的严格定义,NP问题,时间复杂度的分析方法,主定理)
排序算法(平方排序算法的应用,Shell排序,快速排序,归并排序,时间复杂度下界,三种线性时间排序,外部排序)
数论(整除,集合论,关系,素数,进位制,辗转相除,扩展的辗转相除,同余运算,解线性同余方程,中国剩余定理)
指针(链表,搜索判重,邻接表,开散列,二叉树的表示,多叉树的表示)
按位运算(and,or,xor,shl,shr,一些应用)
图论(图论模型的建立,平面图,欧拉公式与五色定理,求强连通分量,求割点和桥,欧拉回路,AOV问题,AOE问题,最小生成树的三种算法,最短路的三种算法,标号法,差分约束系统,验证二分图,Konig定理,匈牙利算法,KM算法,稳定婚姻系统,最大流算法,最小割最大流定理,最小费用最大流算法)
计算几何(平面解几及其应用,向量,点积及其应用,叉积及其应用,半平面相交,求点集的凸包,最近点对问题,凸多边形的交,离散化与扫描)
数据结构(广度优先搜索,验证括号匹配,表达式计算,递归的编译,Hash表,分段Hash,并查集,Tarjan算法,二叉堆,左偏树,二斜堆,二项堆,二叉查找树,红黑树,AVL平衡树,Treap,Splay,静态二叉查找树,2-d树,线段树,二维线段树,矩形树,Trie树,块状链表)
组合数学(排列与组合,鸽笼原理,容斥原理,递推,Fibonacci数列,Catalan数列,Stirling数,差分序列,生成函数,置换,Polya原理)
概率论 (简单概率,条件概率,Bayes定理,期望值)
矩阵(矩阵的概念和运算,二分求解线性递推方程,多米诺骨牌棋盘覆盖方案数,高斯消元)
字符串处理(KMP,后缀树,有限状态自动机,Huffman编码,简单密码学)
动态规划(单调队列,凸完全单调性,树型动规,多叉转二叉,状态压缩类动规,四边形不等式)
博奕论(Nim取子游戏,博弈树,Shannon开关游戏)
搜索(A,ID,IDA ,随机调整,遗传算法)
微积分初步(极限思想,导数,积分,定积分,立体解析几何)
海岛blog icpc
[toc]
https://blog.csdn.net/tigerisland45/article/details/52071939?ops_request_misc=%7B%22request%5Fid%22%3A%22160413384619215646547908%22%2C%22scm%22%3A%2220140713.130102334.pc%5Fall.%22%7D&request_id=160413384619215646547908&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allfirst_rank_v2~rank_v28-4-52071939.pc_search_result_cache&utm_term=icpc&spm=1018.2118.3001.4449
https://blog.csdn.net/sjf0115/article/details/8664562
1.2 ____基础注意事项
*1.2.1____*关于cin解绑cout加速 c语言中的printf,scanf与c++中的cin,cout有所不同。cin、cout是输入输出流,效率较低
,但是便于书写,易懂。C++中,cin和cout要与stdio同步,中间会有一个缓冲
,所以导致cin,cout语句输入输出缓慢,这时就可以用第一个语句,取消cin,cout与stdio的同步,说白了就是提速,效率基本与scanf和printf一致。然后就可放心的使用cin,cout了。
另外第一个语句取消的是cin,cout单独与std的绑定,实际上cin与cout也是绑定的,所以第二条语句就是取消cin与cout的绑定
在这两个绑定都取消后,io流输入效率与print和scanf相同
ios::sync_with_stdio(false); cin.tie(0);
cin
与 scanf
不要混着用,有问题!
1.2.2 ____关于宏定义,typedef与引用(每次都要记错….)#define <宏名> <字符串>
#define PI 3.14
宏定义注意事项
在<字符串>中的式子定义之后不会运算
,仅做字符串存储
例如:
1 2 3 4 5 6 7 #define N 2+2 void main () { int a=N*N; printf (“%d”,a); }
宏N代表的字符串是2+2,正常计算N*N结果为16,但是最终输出为8
原因:计算的式子为 a =2 + 2*2 + 2
直接替换了N的式子
解决办法在定义式子时用“()”定义
#typedef <数据类型> <标识符>
#typedef long long ll
进阶用法
c++创始人写的中的下面这个例子
1 2 3 4 5 6 typedef int P () ;typedef int Q () ;class X { static P (Q) ; static Q (P) ; };
作用定义一个函数P( )用来表示定义一个int类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 typedef int x;x a = 0 ; typedef node{ int far; int rank; }*Ptree , Tree , Trees[3 ]; Tree a1; Trees args; *Ptree = &a1; typedef int *p;p pa;
引用——函数传参
1 2 3 4 5 6 7 8 9 10 void f (int &a, int &b) { a = a + b; } int main () { int x, y; cin >> x >> y; f (x,y); cout << x << endl; return 0 ; }
用&(引用符)传参,可直接使值直接改变到实参上
1.2.3 ____一些测试方法
本地读入测试数据
1 2 3 4 5 6 7 8 9 10 11 12 void file () { #ifdef ONLINE_JUDGE #else freopen ( "d:/workProgram/test.txt" ,"r" ,stdin ); #endif } int main () { file (); }
测试算法运行的时间
1 2 3 4 5 6 7 8 9 10 11 12 13 #include <time.h> clock_t start,stop;int main () { start = clock (); stop = clock (); cout << "the time spend : " << (double )(stop - start) / CLK_TCK; return 0 ; }
随机数
1 2 3 4 5 6 7 8 #include <stdlib.h> int main () { srand ((unsigned )time (NULL )); cout << rand () % 5 << endl; return 0 ; }
1.3 ____特殊函数归纳
__gcd( x , y ) 求最大公倍数
x,y必须是相同类型(int,ll,但不能是浮点数
,)
swap( x , y )
不用担心交换变量精度的缺失,无需构造临时变量,不会增加空间复杂度
reverse( beg , end ) 反转数组从beg位置到end
reverse_copy(beg , end , b) 反转后到新的数组
数组
:reverse( a , a + 10 );
vector
:reverse( v.begin() , v.end() );
string
:reverse( str.begin() , str.end() );
字符数组
:reverse( a , a + strlen(a) );
find(a.begin() , b.begin(), val) 容器查找元素
运用指针
在数组
中实现find
1 2 3 4 5 6 7 8 9 int a[5 ] = {1 ,23 ,3 ,5 ,6 }; int *i = find (a,a+5 ,3 ); int *end = a + 5 ; if ( i == end){ cout << "NO" << endl; } else { cout << i - &a[0 ] <<endl; }
sscanf()
https://blog.csdn.net/TheWaySoFar/article/details/51879481?ops_request_misc=%7B%22request%5Fid%22%3A%22160033252119195162106054%22%2C%22scm%22%3A%2220140713.130102334..%22%7D&request_id=160033252119195162106054&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2alltop_click~default-4-51879481.pc_search_result_cache&utm_term=sscanf&spm=1018.2118.3001.4187
memcpy( a,b,sizeof(int )*k )
将a数组复制
到b数组,k为复制的大小,(int)是数组的类型,如果是直接复制整个数组就去掉*k
*1.4____*动态内存分配
每场比赛,基本上都要用
动态内存分配,越界验证
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 #include <bits/stdc++.h> using namespace std;typedef struct node { int date; node* left_son; node* right_son; }Node; void init (Node* &L,int n) { L = (Node *)malloc ( sizeof (Node) * n); } int main () { int n; cin >> n; Node* L; init (L,n); for (int i = 0 ; i < n; i++){ L[i].date = i; } for (int i = 0 ; i < n; i++){ cout << L[i].date << L[i].date << endl; } printf ("%d\n" ,L[2 ].date); printf ("%d %d\n" ,&L[n + 1 ].date,L[n].date); return 0 ; }
*1.5____*加强for循环
for(auto 元素 : 数据集合)
优点
:在迭代一些容器时很方便,不用写迭代器
缺点
:不能在增强循环里动态的删除集合内容、不能获取下标等。普通for循环可以没有遍历的目标,而增强for循环一定要有遍历的目标。
1 2 3 for (auto item : array){ cout << item << " " ; }
1.6 ____STL概述
stl中包含的头文件
三大类
container容器
algorithm算法
iterator迭代器
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 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 #include <algorithm> #include <deque> 双端队列 #include <queue> 队列 #include <priority_queue> 优先队列 #include <vector> 向量 #include <list> 链表 #include <map> 映射 #include <multimap> 多重映射 #include <set> 集合 #include <multiset> 多重集合 #include <stack> 栈 #include <fucntional> #include <iterator> #include <memory> #include <numeric> #include <utility> vector, 变长数组,倍增的思想 size () 返回元素个数 empty () 返回是否为空 clear () 清空 front ()/back () push_back ()/pop_back () begin ()/end () [] 支持比较运算,按字典序 pair<int , int > first, 第一个元素 second, 第二个元素 支持比较运算,以first为第一关键字,以second为第二关键字(字典序) string,字符串 size ()/length () 返回字符串长度 empty () clear () substr (起始下标,(子串长度)) 返回子串 c_str () 返回字符串所在字符数组的起始地址 queue, 队列 size () empty () push () 向队尾插入一个元素 front () 返回队头元素 back () 返回队尾元素 pop () 弹出队头元素 priority_queue, 优先队列,默认是大根堆 size () empty () push () 插入一个元素 top () 返回堆顶元素 pop () 弹出堆顶元素 定义成小根堆的方式:priority_queue<int , vector<int >, greater<int >> q; stack, 栈 size () empty () push () 向栈顶插入一个元素 top () 返回栈顶元素 pop () 弹出栈顶元素 deque, 双端队列 size () empty () clear () front ()/back () push_back ()/pop_back () push_front ()/pop_front () begin ()/end () [] set, map, multiset, multimap, 基于平衡二叉树(红黑树),动态维护有序序列 size () empty () clear () begin ()/end () ++, -- 返回前驱和后继,时间复杂度 O (logn) set/multiset insert () 插入一个数 find () 查找一个数 count () 返回某一个数的个数 erase () (1 ) 输入是一个数x,删除所有x O (k + logn) (2 ) 输入一个迭代器,删除这个迭代器 lower_bound () /upper_bound () lower_bound (x) 返回大于等于x的最小的数的迭代器 upper_bound (x) 返回大于x的最小的数的迭代器 map/multimap insert () 插入的数是一个pair erase () 输入的参数是pair或者迭代器 find () [] 注意multimap不支持此操作。 时间复杂度是 O (logn) lower_bound () /upper_bound () unordered_set, unordered_map, unordered_multiset, unordered_multimap, 哈希表 和上面类似,增删改查的时间复杂度是 O (1 ) 不支持 lower_bound () /upper_bound () , 迭代器的++,-- bitset, 圧位 bitset<10000> s ; ~, &, |, ^ >>, << ==, != [] count () 返回有多少个1 any () 判断是否至少有一个1 none () 判断是否全为0 set () 把所有位置成1 set (k, v) 将第k位变成v reset () 把所有位变成0 flip () 等价于~ flip (k) 把第k位取反 **查找数组最小值,最大值** *min_elemant (a,a+n); *max_elemant (a,a+n);
1.7 ____快读快输
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 inline int read () {int x=0 ,f=1 ;char ch=getchar ();while (ch<'0' ||ch>'9' ){ if (ch=='-' )f=-1 ; ch=getchar (); } while (ch>='0' &&ch<='9' ){x=(x<<1 )+(x<<3 )+(ch^48 ); ch=getchar (); } return x*f;} inline int write (int X) {if (X<0 ) {putchar ('-' ); X=~(X-1 );} int s[20 ],top=0 ;while (X) {s[++top]=X%10 ; X/=10 ;} if (!top) s[++top]=0 ; while (top) putchar (s[top--]+'0' );}
1.8 ____关于取模
在大数相加中,如果只是求后N位,在每次相加后取1eN的模对结果无影响
1.9 ____bitset基本操作
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 bitset<8> n; n = 2 ; cout << n << endl; cout << n[1 ] << endl; cout << n.count () << endl; cout << n.size () << endl; cout << n.set (2 ) <<endl; n[3 ] = 1 ; cout << n << endl; n[3 ] = 0 ; cout << n << endl; cout << n.reset (1 ) << endl; cout << n.set () << endl; cout << n.reset () << endl; cout << "/////////" << endl; cout << n.flip () << endl; cout << n[2 ].flip () << endl; n.flip (2 ); cout << n << endl; unsigned long nb = n.to_ulong (); cout << nb << endl; cout<<"bs1.any() = " <<bs1.any ()<<endl; bitset<3> bsNone; cout<<"bsNone.none() = " <<bsNone.none ()<<endl;
1.10 ____关于floor对小数的向下取整floor()函数不管是对int型还是double型变量向下取整后都是整数
ceil同理
1.11 ____关于stl中end(),begin(),rbegin(),rend()
1.12 ____关于取模
1.13 ____String 与 char[ ] 读入
对于string
:
可输入空格的一行读入
1 2 string s; getline (cin.s);
直接读入不可输入空格
对于char[ ]
:
限定大小的输入( 不能读入String )
1 2 3 char s[maxn];cin.get (a,n); cin.getline (a,n);
可输入空格一行读入
1.14 ____String 与 char[ ] 的函数
关于 string的知识点
可以使用 “ = “进行赋值,使用 “ == “进行等值比较,使用 “ + ”做串联
string类支持 cin , getline( cin , s ) 两种输入方式
string可用 s.max_size()
输出最大容量
C艹的String接口与函数
string的所有成员函数
String
string的其他函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 transform (str.begin (), str.end (), str.begin (), ::tolower); cout << "转小写: " << str << endl; transform (str.begin (), str.end (), str.begin (), ::toupper); cout << "转大写: " << str << endl; int a = 123 ;string num = to_string (a); string num = "123124" ; int a = stoi (num);
char 转 int 1 2 3 4 5 6 7 8 9 char *str = "1231245" ;int a = atoi (str);char num[10 ];int a = 213 ;sprintf (num,"%d" ,a);
1.15 ____任意进制转换
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 75 76 77 78 79 80 81 82 #include <iostream> #include <algorithm> #include <cstring> #include <cstdio> using namespace std; const int maxn = 200 ;int num[maxn]; char s[maxn],ans[2000 ],te[2000 ]; int x ,y; int getnum (char c ) { if ( c >= '0' && c <= '9' ) return c - '0' ; else if ( c >= 'A' && c <= 'Z' ) return c + 10 - 'A' ; else if ( c >= 'a' && c <= 'z' ) return c + 36 - 'a' ; } char getchar (int c ) { if ( c >= 0 && c <= 9 ) return c + '0' ; else if ( c >= 10 && c <= 35 ) return c - 10 + 'A' ; else if ( c >= 36 && c <= 61 ) return c - 36 + 'a' ; } void trans ( ){ int len = strlen( s ); for (int i = 0 ; i < len ; i++) num[i] = getnum( s[i] ); int k = -1 ; for ( int j = 0 ; j <len ; ){ for (int i = j ; i < len - 1 ; i++) { num[i + 1 ] += num[i] % y * x; num[i] /= y; } te[++k] = num[len - 1 ] % y; num[len - 1 ] /= y; while ( j < len && !num[j] ) j++; for (int i = 0 ; i <= k ; i++) ans[i] = getchar( te[k - i] ); } cout << x << ' ' ; printf("%s\n" ,s); cout << y << ' ' ; for (int i = 0 ; i <= k ; i++){ cout << ans[i]; } cout << endl << endl; } int main ( ) { int t; cin >> t; while (t--) { memset(s,0 ,sizeof s); memset(num,0 ,sizeof num); memset(ans,0 ,sizeof ans); memset(te,0 ,sizeof te); cin >> x >> y; cin >> s; trans(); } return 0 ; }
1.16____数据范围
一般ACM或者笔试题的时间限制是1秒或2秒。 在这种情况下,C++代码中的操作次数控制在 $10^6$∼$10^8$ $10^7$∼$10^8$ 为最佳。
下面给出在不同数据范围下,代码的时间复杂度和算法该如何选择:
n≤30n≤30, 指数级别, dfs+剪枝,状态压缩dp n≤100n≤100 => O(n3)O(n3),floyd,dp,高斯消元 n≤1000n≤1000 => O(n2)O(n2),O(n2logn)O(n2logn),dp,二分,朴素版Dijkstra、朴素版Prim、Bellman-Ford n≤10000n≤10000 => O(n∗n√)O(n∗n),块状链表、分块、莫队 n≤100000n≤100000 => O(nlogn)O(nlogn) => 各种sort,线段树、树状数组、set/map、heap、拓扑排序、dijkstra+heap、prim+heap、spfa、求凸包、求半平面交、二分、CDQ分治、整体二分 n≤1000000n≤1000000 => O(n)O(n), 以及常数较小的 O(nlogn)O(nlogn) 算法 => 单调队列、 hash、双指针扫描、并查集,kmp、AC自动机,常数比较小的 O(nlogn)O(nlogn) 的做法:sort、树状数组、heap、dijkstra、spfa n≤10000000n≤10000000 => O(n)O(n),双指针扫描、kmp、AC自动机、线性筛素数 n≤109n≤109 => O(n√)O(n),判断质数 n≤1018n≤1018 => O(logn)O(logn),最大公约数,快速幂 n≤101000n≤101000 => O((logn)2)O((logn)2),高精度加减乘除 n≤10100000n≤10100000 => O(logk×loglogk),k表示位数O(logk×loglogk),k表示位数,高精度加减、FFT/NTT
1.17____cin格式化输出 1.18____cout格式化输出 1 2 3 4 cout.setf (ios::fixed); cout << setprecision (4 ) << x <<endl;
1.19____ count_if()
函数 返回满足 第三个参数这个函数的数量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #include <bits/stdc++.h> using namespace std;bool f3 (int x) { return x % 3 == 0 ; };int main () { ios::sync_with_stdio (false ); cin.tie (0 ); vector<int > vec = {1 ,3 ,9 ,6 ,23 ,21 ,23 ,20 ,30 ,33 }; int cnt = count_if ( vec.begin (), vec.end () , f3 ); cout <<cnt <<endl; return 0 ; }
1.20____generate()
函数 以第三个参数给区间赋值,第三个参数是不接受任何参数的函数对象
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 #include <bits/stdc++.h> using namespace std;bool f3 (int x) { return x % 3 == 0 ; };int main () { ios::sync_with_stdio (false ); cin.tie (0 ); srand ((unsigned )time (0 )); vector<int > vec = {1 ,3 ,9 ,6 ,23 ,21 ,23 ,20 ,30 ,33 }; for ( auto it = vec.begin () ;it != vec.end () ; it++ ){ cout << *it << ' ' ; } cout << endl; generate (vec.begin () , vec.end (), std::rand); for ( auto it = vec.begin () ;it != vec.end () ; it++ ){ cout << *it << ' ' ; } cout << endl; return 0 ; }
1.21____lambda
表达式 labmda
表达式这个系统让你能够使用匿名函数——即无需给函数命名。在C++11
中对于接受函数指针或函数符的函数,可使用匿名函数定义(lambda)作为参数
1 [] (int x) { return x % 3 == 0 ;}
上面count_if
的函数
1 bool f3 (int x ) { return x%3 ==0 ; }
两者的区别就是将bool f3
(返回值类型以及函数名)替换为了[]
(这就是匿名的由来);没有声明返回值类型。返回类型相当于使用 decltyp
根据推断得到的,这里为bool
。
一段简单的Code
1 2 3 4 5 6 7 8 9 10 11 #include <iostream> using namespace std; int main () { int a = 1 ; int b = 2 ; auto func = [=, &b](int c)->int {return b += a + c;}; return 0 ; }
当我第一次看到这段代码时,我直接凌乱了,直接看不懂啊。上面这段代码,如果你看懂了,下面的内容就当时复习了;如果看不懂了,就接着和我一起总结吧。
基本语法
简单来说,Lambda函数也就是一个函数,它的语法定义如下:
[capture](parameters) mutable ->return-type{statement}
1.[capture]:捕捉列表。捕捉列表总是出现在Lambda函数的开始处。实际上,[]是Lambda引出符。编译器根据该引出符判断接下来的代码是否是Lambda函数。捕捉列表能够捕捉上下文中的变量以供Lambda函数使用;
2.(parameters):参数列表。与普通函数的参数列表一致。如果不需要参数传递,则可以连同括号“()”一起省略;
3.mutable:mutable修饰符。默认情况下,Lambda函数总是一个const函数,mutable可以取消其常量性。在使用该修饰符时,参数列表不可省略(即使参数为空);
4.->return-type:返回类型。用追踪返回类型形式声明函数的返回类型。我们可以在不需要返回值的时候也可以连同符号”->”一起省略。此外,在返回类型明确的情况下,也可以省略该部分,让编译器对返回类型进行推导;
5.{statement}:函数体。内容与普通函数一样,不过除了可以使用参数之外,还可以使用所有捕获的变量。
与普通函数最大的区别是,除了可以使用参数以外,Lambda函数还可以通过捕获列表访问一些上下文中的数据。具体地,捕捉列表描述了上下文中哪些数据可以被Lambda使用,以及使用方式(以值传递的方式或引用传递的方式)。语法上,在“[]”包括起来的是捕捉列表,捕捉列表由多个捕捉项组成,并以逗号分隔。捕捉列表有以下几种形式:
1.[var]表示值传递方式捕捉变量var; 2.[=]表示值传递方式捕捉所有父作用域的变量(包括this); 3.[&var]表示引用传递捕捉变量var; 4.[&]表示引用传递方式捕捉所有父作用域的变量(包括this); 5.[this]表示值传递方式捕捉当前的this指针。
上面提到了一个父作用域,也就是包含Lambda函数的语句块,说通俗点就是包含Lambda的“{}”代码块。上面的捕捉列表还可以进行组合,例如:
1.[=,&a,&b]表示以引用传递的方式捕捉变量a和b,以值传递方式捕捉其它所有变量; 2.[&,a,this]表示以值传递的方式捕捉变量a和this,引用传递方式捕捉其它所有变量。
不过值得注意的是,捕捉列表不允许变量重复传递。下面一些例子就是典型的重复,会导致编译时期的错误。例如:
3.[=,a]这里已经以值传递方式捕捉了所有变量,但是重复捕捉a了,会报错的; 4.[&,&this]这里&已经以引用传递方式捕捉了所有变量,再捕捉this也是一种重复。
Lambda的使用
对于Lambda的使用,说实话,我没有什么多说的,个人理解,在没有Lambda之前的C++ , 我们也是那样好好的使用,并没有对缺少Lambda的C++有什么抱怨,而现在有了Lambda表达式,只是更多的方便了我们去写代码。不知道大家是否记得C++ STL库中的仿函数对象,仿函数想对于普通函数来说,仿函数可以拥有初始化状态,而这些初始化状态是在声明仿函数对象时,通过参数指定的,一般都是保存在仿函数对象的私有变量中;在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;typedef enum { add = 0 , sub, mul, divi } type; class Calc { public : Calc (int x, int y):m_x (x), m_y (y) {} int operator () (type i) { switch (i) { case add: return m_x + m_y; case sub: return m_x - m_y; case mul: return m_x * m_y; case divi: return m_x / m_y; } } private : int m_x; int m_y; }; int main () { Calc addObj (10 , 20 ) ; cout<<addObj (add)<<endl; return 0 ; }
现在我们有了Lambda这个利器,那是不是可以重写上面的实现呢?看代码:
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 \#include <iostream> using namespace std;typedef enum { add = 0 , sub, mul, divi }type; int main () { int a = 10 ; int b = 20 ; auto func = [=](type i)->int { switch (i) { case add: return a + b; case sub: return a - b; case mul: return a * b; case divi: return a / b; } }; cout<<func (add)<<endl; }
显而易见的效果,代码简单了,你也少写了一些代码,也去试一试C++中的Lambda表达式吧。
关于Lambda那些奇葩的东西
看以下一段代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #include <iostream> using namespace std; int main () { int j = 10 ; auto by_val_lambda = [=]{ return j + 1 ; }; auto by_ref_lambda = [&]{ return j + 1 ; }; cout<<"by_val_lambda: " <<by_val_lambda ()<<endl; cout<<"by_ref_lambda: " <<by_ref_lambda ()<<endl; ++j; cout<<"by_val_lambda: " <<by_val_lambda ()<<endl; cout<<"by_ref_lambda: " <<by_ref_lambda ()<<endl; return 0 ; }
程序输出结果如下:
1 2 3 4 by_val_lambda: 11 by_ref_lambda: 11 by_val_lambda: 11 by_ref_lambda: 12 你想到了么???那这又是为什么呢?为什么第三个输出不是12 呢?
在by_val_lambda中,j被视为一个常量,一旦初始化后不会再改变(可以认为之后只是一个跟父作用域中j同名的常量),而在by_ref_lambda中,j仍然在使用父作用域中的值。所以,在使用Lambda函数的时候,如果需要捕捉的值成为Lambda函数的常量,我们通常会使用按值传递的方式捕捉;相反的,如果需要捕捉的值成成为Lambda函数运行时的变量,则应该采用按引用方式进行捕捉。
再来一段更晕的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #include <iostream> using namespace std; int main () { int val = 0 ; auto mutable_val_lambda = [=]() mutable { val = 3 ; }; mutable_val_lambda (); cout<<val<<endl; auto const_ref_lambda = [&]() { val = 4 ; }; const_ref_lambda (); cout<<val<<endl; auto mutable_ref_lambda = [&]() mutable { val = 5 ; }; mutable_ref_lambda (); cout<<val<<endl; return 0 ; }
这段代码主要是用来理解Lambda表达式中的mutable关键字的。默认情况下,Lambda函数总是一个const函数,mutable可以取消其常量性。按照规定,一个const的成员函数是不能在函数体内修改非静态成员变量的值。例如上面的Lambda表达式可以看成以下仿函数代码:
1 2 3 4 5 6 7 8 9 class const_val_lambda { public : const_val_lambda (int v) : val (v) {} void operator () () const { val = 3 ; } private : int val; };
对于const的成员函数,修改非静态的成员变量,所以就出错了。而对于引用的传递方式,并不会改变引用本身,而只会改变引用的值,因此就不会报错了。都是一些纠结的规则。慢慢理解吧。
总结
对于Lambda这种东西,有的人用的非常爽,而有的人看着都不爽。仁者见仁,智者见智。不管怎么样,作为程序员的你,都要会的。这篇文章就是用来弥补自己对C++ Lambda表达式的认知不足的过错,以免以后在别人的代码中看到了Lambda,还看不懂这种东西,那就丢大人了。
1.22____再谈二进制 1.22.1____输出一个数的二进制 假定是一个32位int的数
1 2 3 4 5 6 7 8 9 10 11 #include <bits/stdc++.h> using namespace std;int main () { int te = 1287361963 ; for (int i = 31 ; i >= 0 ; i--){ cout << ((te>>i)&1 ); } return 0 ; }
1.22.2____判断奇数,偶数 1 2 3 4 5 6 if ( x & 1 ){ } else if ( !(x&1 ) ){ }
2 ____算法
2.1____哈夫曼树
哈夫曼树,又名最优二叉树
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 int n,a; cin>>n; priority_queue<int , vector<int >, greater<int > >q; for (int i=0 ; i<n; i++) { scanf ("%d" ,&a); q.push (a); } while (q.size ()>1 ) { int l1,l2; l1 = q.top (); q.pop (); l2 = q.top (); q.pop (); ans += (l1 + l2); q.push (l1+l2); } printf ("%lld\n" ,ans);
2.2____ 全排列
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 const int maxn = 10 ;int n;int p[maxn];int Hash[maxn] = {false };void f (int index) { if (index == n + 1 ){ for (int i = 1 ; i <= n; i++){ cout << p[i]; } cout << endl; return ; } for (int x = 1 ; x <= n ; x++){ if ( Hash[x] == false ){ p[x] = index; Hash[x] = true ; f (index + 1 ); Hash[x] = false ; } } } int main () { cin >> n; f (1 ); }
next_permutation()
对一个升序的序列进行全排列
prev_permutation()
对一个降序的序列进行全排列
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <iostream> #include <algorithm> #include <cstring> using namespace std;int main () { char str[10 ],ans[10 ]; int len; scanf ("%s" ,str); len = strlen (str); do { printf ("%s\n" ,str); } while ( prev_permutation (str,str + len) ); return 0 ; }
2.3____快速幂
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 typedef long long LL;LLpow (LL a, LL b,LL m){ if (b == 0 ){ return 1 ; } if ( b%2 == 0 )return a*LLpow (a, b-1 ,m) % m; else { LL mul = LLpow (a, b/2 ,m); return mul*mul % 2 ; } } LL qmi (LL a, LL b, LL p) { LL res = 1 % p; while (b) { if (b & 1 ) res = (LL)res * a % p; a = (LL)a * a % p; b >>= 1 ; } return res; }
2.4 ____大斐波那契数列(1e9)
首先斐波那契通项式
求前4项对数法【HDU -1568】 ()
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 #include <cstdio> #include <cstring> #include <cmath> double cnt=(1 +sqrt (5 ))/2 ;int a[21 ]={0 ,1 };int main () { int n,i; for (i=2 ;i<21 ;++i) a[i]=a[i-1 ]+a[i-2 ]; while (scanf ("%d" ,&n)!=EOF) { if (n<21 ) printf ("%d\n" ,a[n]); else { double ans=-0.5 *log10 (5.0 )+n*log10 (cnt); ans=ans-floor (ans); ans=pow (10 ,ans); ans=floor (ans*1000 ); printf ("%.0f\n" ,ans); } } return 0 ; }
2.5 ____二进制枚举1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #include <bits/stdc++.h> using namespace std;int main () { bitset<4> a; int n[8 ]= {1 ,4 ,2 ,7 ,5 ,8 ,0 }; for (int i = 1 ; i < 1 << 5 ; i++){ a = i; int sum = 0 ; for (int j = 0 ; j <= 3 ; j++){ if (a[j] == 1 ){ sum += n[j]; } } cout << a << " " << sum <<endl; } return 0 ; }
https://www.acwing.com/problem/content/description/833/
2.6 ____离散化
#include ///哈希表 一个序列0….4…6….13213….1e
虽然这个序列的数值非常大,但是选取的值非常
将大的数映射mapping到从0开始的自然数
这个过程就叫做离散化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 vector<int > alls; sort (alls.begin (), alls.end ()); alls.erase (unique (alls.begin (), alls.end ()), alls.end ()); int find (int x) { int l = 0 , r = alls.size () - 1 ; while (l < r){ int mid = (l + r) >> 1 ; if ( a[mid] < x ) l = mid + 1 ; else r = mid; } return r + 1 ; }
2.7 ____前缀和
有个序列a1,a2,a3,a4,a5…..
定义
同时
s1 = a1;
s2 = a1 + a2; s2 = s1 + a2 ;
s3 = a1 + a2 +a3; s3 = s2 + a3 ;
…..
我们把s序列称为a序列的前缀和
作用
一、求前i个数的和 Sn = S1 + S2 +…. Sn-1 + Sn
查询时间复杂度为O(1);
二、求[ l , r ]区间的和 公式: S[l ,r] = Sr - S(l - 1);
Sr = a1 + a2 + … + al +… + ar
S(l -1) = a1 +… a(l-1);
从1开始统计前缀和 —— 便于处理边界问题
二维前缀和
计算子矩阵的和公式:
公式: S= S[ x2 ][ y2 ] - S[ x1 - 1 ,y2 ] - S[ x2 , y1 - 1 ] + S[ x1 - 1, y1 - 1 ]
计算S[ i , j ]公式:(之前的几行已经都计算完了)(建立前缀矩阵)(就是绿色的那个点,应该等于多少)
公式: S[i,j] = S[ i - 1, j ] + S[ i , j - 1 ] - S[ i - 1, j -1 ] + a[ i , j ];
前缀数组
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 const int maxn = 1000 +5 ;int n,m,t;int a[maxn][maxn];int pre[maxn][maxn];int main () { cin >> n >> m >> t; memset (a,0 ,sizeof (a)); memset (pre,0 ,sizeof (pre)); for (int i = 1 ; i <= n; i++){ for (int j = 1 ; j <= m; j++){ cin >> a[i][j] ; pre[i][j] = pre[ i -1 ][j] + pre[i][ j - 1 ] - pre[ i - 1 ][j - 1 ] + a[i][j]; } } for (int i = 0 ; i < t; i ++){ int x1,y1,x2,y2; cin >> x1 >> y1 >> x2 >> y2; cout << pre[ x2 ][ y2 ] - pre[ x1 - 1 ][y2 ] - pre[ x2 ][ y1 - 1 ] + pre[ x1 - 1 ][ y1 - 1 ] << endl; } return 0 ; }
2.8 ____差分
差分数组las[][](或者b[][])的一个点增,减值是对后面的元素产生影响,所有是在右下矩阵进行操作(而前缀数组影响的是前面的值,所以修改的是左上角的矩阵),且每一个点,代表一整个a数组的子矩阵,所以只需要对单个点进行修改即可。
在初始化差分数组的时候也是用相同的方法把a[i][j]的元素插入进入,之后的改变值就是插入c
差分核心公式:(c为改变的量)
b[x1][y1] += c;
b[x1][y2 + 1] -= c;
b[x2 + 1][y1] -= c;
b[x2][y2] += 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 int n,m,t;const int maxn = 1e4 +5 ;int a[maxn][maxn];int las[maxn][maxn];void insert (int x1,int y1,int x2,int y2,int c) { las[x1][y1] += c; las[x1][y2 + 1 ] -= c; las[x2 + 1 ][y1] -= c; las[x2 + 1 ][y2 + 1 ] += c; } int main () { scanf ("%d%d%d" ,&n,&m,&t); for (int i = 1 ; i <= n; i++ ){ for (int j = 1 ; j <= m; j++){ scanf ("%d" ,&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]); } } for (int i = 0 ; i < t; i++){ int x1,x2,y1,y2,c; scanf ("%d%d%d%d%d" ,&x1,&y1,&x2,&y2,&c); insert (x1, y1, x2, y2, c); } for (int i = 1 ; i <= n; i++){ for (int j = 1 ; j <= m; j++){ a[i][j] = a[ i -1 ][j] + a[i][ j - 1 ] - a[ i - 1 ][j - 1 ] + las[i][j]; cout << a[i][j]; if (j != m) cout << " " ; } cout << endl; } }
2.9 ____二分
lower_bound
int a[maxn]; loc = lower_bound(a,a+n,x) - a
1 2 3 4 5 6 int l = 0 , r = n - 1 ; while (l < r) { int mid = l + r >> 1 ; if (a[mid] < x) l = mid + 1 ; else r = mid; }
upper_bound
int a[maxn]; loc = upper_bound(a,a+n,x) - a -1
1 2 3 4 5 6 int l = 0 , r = n - 1 ; while (l < r) { int mid = l + r + 1 >> 1 ; if (a[mid] <= x) l = mid; else r = mid - 1 ; }
浮点二分
1 2 3 4 5 6 7 8 9 double l,r; l = 0 ; r = 1e9 ; while ( r - l > 1e-4 ){ double mid = ( l + r) / 2 ; if ( f (mid) ) l = mid; else r = mid; }
二分不光可以查找值
1.最大化最小值
upper_bound
POJ 3258
枚举的是当最短的距离为多少事,移除的石头刚好为m
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 typedef long long ll;const int maxn = 5e5 ;const int INF = 0x3f3f3f3f ;int a[maxn];int l, n , m;bool f (int x) { int p = 0 ; int cnt = 0 ; for (int i = 1 ; i <= n + 1 ; i++){ if ( a[i] - a[p] < x )cnt ++; else p = i; } return cnt <= m; } int main () { cin >> l >> n >> m; for (int i = 1 ; i <= n; i ++ ){ cin >> a[i]; } a[0 ] = 0 ; a[n + 1 ] = l; sort (a, a +n+2 ); int le = 0 ; int re = INF; while (le < re) { int mid = le + re + 1 >> 1 ; if ( f (mid) ) le = mid; else re = mid - 1 ; } cout << le <<endl; return 0 ; }
2.最大化平均值 2.1.10____尺取法,Two-Pointer ,双指针算法
对于O( n^2 )的算法
1 2 3 for (int i = 0 ;i < n ; i++) for (int j = 0 ; j < n; j++)
如果利用双指针遍历,会是 O ( n ) 的
所以当有两重for循环的O ( n )算法可以考虑用双指针
2.1.11 ____三分
2.1.12 ____枚举
一维状态枚举( 翻硬币 )
https://www.acwing.com/problem/content/1210/
二维状态枚举( 熄灯问题 )
2.1.13____日期题
基母拉尔森公式
计算是某天是星期几的公式
$W= (d+2*m+3(m+1)/5+y+y/4-y/100+y/400+1)%7$
在公式中d表示日期中的日数,m表示月份数,y表示年数。
注意:在公式中有个与其他公式不同的地方:
把一月和二月看成是上一年的十三月和十四月,例:如果是2004-1-10则换算成:2003-13-10来代入公式计算。 输入年月日到公式,返回当天的星期数。
1 2 3 4 5 6 int week (int y, int m, int d) ** { if (m == 1 || m == 2 ) m = m + 12 , y--; return (d + 2 * m + 3 * (m + 1 ) / 5 + y + y / 4 - y / 100 + y / 400 + 1 ) % 7 ; }
时分秒计算
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #include <stdio.h> int get_time () { int h1,h2,m1,m2,s1,s2; scanf ("%d:%d:%d %d:%d:%d" ,&h1,&m1,&s1,&h2,&m2,&s2); int day = 0 ; if ((getchar ())!='\n' ) scanf ("(+%d)" ,&day); int S = h1*3600 + m1*60 + s1; int E = h2*3600 + m2*60 + s2; return E - S + day*24 *3600 ; } int main () { int n; scanf ("%d" ,&n); while (n--){ int ans =(get_time () + get_time ())/2 ; printf ("%02d:%02d:%02d\n" ,ans/3600 ,ans/60 %60 ,ans%60 ); } return 0 ; }
2.1.13____模拟退火
模拟退火是求最优化的算法。
用于求函数最值问题,TSP旅行商问题,最小园覆盖问题,最小球覆盖问题等。
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 <bits/stdc++.h> using namespace std;struct Point { double x,y; }p[105 ]; double eps = 1e-8 ;int n;double fun (Point tep) { double teans = 0 ; for (int i = 0 ; i < n ; i++) { teans += hypot ( tep.x - p[i].x , tep.y - p[i].y ); } return teans; } double solve () { double T = 10000 ; double delta = 0.97 ; Point nowp; nowp.x = 5000 ,nowp.y = 5000 ; double now = fun (nowp); double ans = now; while ( T > eps ) { int f[2 ] = {1 ,-1 }; Point newp = {nowp.x + f[rand ()%2 ] * T , nowp.y + f[rand ()%2 ] * T }; if ( newp.x >= 0 && newp.x <= 10000 && newp.y >= 0 && newp.y <= 10000 ) { double next = fun (newp); ans = min (ans,next); if ( now - next > eps) { nowp = newp , now = next; } } T *= delta; } return ans; } int main () { srand ((unsigned )time (NULL )); scanf ("%d" ,&n); for (int i = 0 ; i < n ; i ++) { scanf ("%lf%lf" ,&p[i].x ,&p[i].y); } double an = solve (); an +=0.5 ; printf ("%d\n" ,(int )an ); }
3 ____数据结构
3.1 ____单链表
应用于后面图与树的存储
头节点(根节点)head
e[N]表示这个节点的值,ne[N]表示下一个节点的位置
*3.2____*树的遍历与建树
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 const int maxn = 55 ;struct node { int date; node* left_son; node* right_son; }; int pre[maxn],in[maxn],post[maxn];int n;node* create (int postL,int postR,int inL,int inR) { if (postL > postR){ return NULL ; } node* root = new node; root -> date = post[postR]; int k; for ( k = inL; k <= inR; k++){ if (in[k] == post[postR]){ break ; } } int numLeft = k - inL; root -> left_son = create (postL, postL + numLeft - 1 , inL, k - 1 ); root -> right_son = create (postL + numLeft, postR - 1 , k + 1 , inR); return root; } int num = 0 ;void BFS (node* root) { queue<node*> q; q.push (root); while ( !q.empty () ){ node* qt = q.front (); q.pop (); cout << qt -> date ; if (num < n)cout << " " ; num++; if (qt -> left_son != NULL ){ q.push (qt -> left_son); } if (qt -> right_son != NULL ){ q.push (qt -> right_son); } } }
3.3 ____并查集
种类并查集(k倍数组)
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 #include <iostream> #include <cstdio> using namespace std;const int MAX = 50000 + 5 ;int a[MAX*3 ];void init (int n) { for (int i = 1 ; i <= n*3 ; i++){ a[i] = i; } } int Find (int x) { if (a[x] == x){ return x; } else return a[x] = Find (a[x]); } void marge (int x, int y) { x = Find (x); y = Find (y); if ( x== y) return ; a[x] = y; } int main () { ios::sync_with_stdio (false ); cin.tie (0 ); int n,m; scanf ("%d%d" ,&n,&m); init (n); int ans = 0 ; for (int i = 0 ; i < m; i++){ int t,x,y; scanf ("%d%d%d" ,&t,&x,&y); if (x > n || y > n){ ans++; continue ; } if (x == y && t == 2 ){ ans++; continue ; } if (t == 1 ){ if ( Find (y)==Find (x+2 *n)||Find (x)==3F ind(y+2 *n)){ans++;continue ;} marge (x,y); marge (x+n,y+n); marge (x+2 *n,y+2 *n); } else { if (Find (x)==Find (y)||Find (x)==Find (y+2 *n)){ans++;continue ;} marge (x+2 *n,y); marge (x+n,y+2 *n); marge (x,y+n); } } cout << ans << endl; }
3.4 ____线段树
参考博客 ,狂兵专题题解
可能的书写错误
[rt >> 1]
写成[rt << 1]
忘记build()
==
与 =
线段树是一颗完美二叉树,我们最开始输入的时候直接输入到数组
单点更新,区间查询
建树
首先将左右节点的值初始化了之后,再去初始化根节点,递归的建树
开始我没有用struct结构来做线段树的题,但是做了更多的题之后,我发现,单纯的数组格式有很多的限制,元素与元素的传参很复杂,当指标过多的时候无法传递,所有还是改为了struct来存
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 int A[N];struct Node { int l,r; int sum; }tree[N*4 ]; void push_up (int rt) { tree[rt].sum = tree[rt << 1 ].sum + tree[rt << 1 |1 ].sum; } void build (int rt ,int l, int r) { tree[rt].l = l,tree[rt].r = r; if (l == r) { tree[rt].sum = A[r]; } else { int mid = (l + r) >> 1 ; build (rt << 1 , l ,mid); build (rt <<1 |1 ,mid + 1 , r); push_up (rt); } }
单点修改
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 void Update (int rt ,int pos , int val) { if ( tree[rt].l == tree[rt].r ) { A[pos] += val; tree[rt].sum += val; return ; } else { int mid = ( tree[rt].l + tree[rt].r ) >> 1 ; if ( pos <= mid ) Update (rt << 1 , pos,val); else Update ( rt << 1 |1 , pos ,val ); push_up (rt); } }
区间求和
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 int Query (int rt,int l, int r) { if ( l <= tree[rt].l && r >= tree[rt].r ) return tree[rt].sum; if ( tree[rt].l > r || tree[rt].r < l ) return 0 ; else { int mid = (tree[rt].l + tree[rt].r) >> 1 ; int sum_l ,sum_r; sum_l = sum_r = 0 ; if ( l <= mid )sum_l = Query (rt << 1 , l , r); if ( r > mid ) sum_r = Query (rt << 1 |1 , l,r ); return sum_l + sum_r; } }
区间更新,区间查询 区间更新的建树
过程和单点更新一样
区间更新与lazy标记下传
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 void push_down (int rt ,int l ,int r) { if ( tree[rt].lazy != 0 ) { int lazy = tree[rt].lazy; int mid = ( l + r) >> 1 ; tree[rt << 1 ].sum += ( mid - l + 1 ) * lazy; tree[rt << 1 |1 ].sum += ( r - mid ) * lazy; tree[rt << 1 ].lazy += lazy; tree[rt << 1 |1 ].lazy += lazy; tree[rt].lazy = 0 ; } } void rangeUpdate (int rt,int l ,int r,int val) { if ( l <= tree[rt].l && tree[rt].r <= r ) { tree[rt].sum += val * ( tree[rt].r - tree[rt].l + 1 ); tree[rt].lazy += val; } push_down (rt,tree[rt].l,tree[rt].r); int mid = ( tree[rt].l + tree[rt].r ) >> 1 ; if ( l <= mid ) rangeUpdate (rt << 1 ,l ,r ,val); if ( r >= mid + 1 ) rangeUpdate (rt << 1 |1 ,l,r,val ); push_up (rt); }
带lazy下传的区间查询
1 2 3 4 5 6 7 8 9 10 11 12 13 ll rangeQuery (int rt,int l,int r) { if ( l <= tree[rt].l && tree[rt].r <= r ) return tree[rt].sum; push_down (rt,tree[rt].l,tree[rt].r); int mid = (tree[rt].l + tree[rt].r) >> 1 ; int l_son,r_son; l_son = r_son = 0 ; if ( l <= mid ) l_son = rangeQuery ( rt << 1 ,l ,r ); if ( r > mid ) r_son = rangeQuery (rt << 1 |1 , l ,r); return l_son + r_son; }
3.4.1 ____区间染色问题
Count the Colors
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 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 #include <bits/stdc++.h> using namespace std;const int N = 8005 ;int A[N];int cnt[N];struct Node { int l,r; int date = -1 ; int lazy = - 1 ; }tree[N*4 ]; int maxn = 0 ;void push_up (int rt) { if ( tree[rt << 1 ].date == -2 || tree[rt << 1 |1 ].date == -2 ) tree[rt].date = -2 ; else if ( tree[rt << 1 ].date == tree[rt << 1 |1 ].date && tree[rt << 1 ].date == -1 ) tree[rt].date = -1 ; else if ( tree[rt << 1 ].date == -1 && tree[rt << 1 |1 ].date != -1 ) tree[rt].date = tree[rt << 1 |1 ].date; else if ( tree[rt << 1 ].date != -1 && tree[rt <<1 |1 ].date == -1 ) tree[rt].date = tree[rt << 1 ].date; } void build (int rt,int l,int r) { tree[rt].l = l , tree[rt].r = r; if ( l == r ) { tree[rt].lazy = tree[rt].date = -1 ; return ; } int mid= (l + r) >> 1 ; build (rt << 1 , l ,mid); build (rt << 1 |1 ,mid +1 ,r); } void push_down (int rt,int l ,int r) { if ( tree[rt].lazy != -1 ) { int lazy = tree[rt].lazy; tree[rt << 1 ].date = lazy; tree[rt << 1 |1 ].date = lazy; tree[rt <<1 |1 ].lazy = lazy; tree[rt << 1 ].lazy = lazy; tree[rt].lazy = -1 ; } } void rangeUpdate (int rt,int l,int r,int val) { if ( l <= tree[rt].l && r >= tree[rt].r ) { tree[rt].date = val; tree[rt].lazy = val; return ; } else if ( tree[rt].l > r || tree[rt].r < l ) return ; else { int mid = ( tree[rt].l +tree[rt].r ) >> 1 ; push_down (rt,tree[rt].l,tree[rt].r); if (l <= mid) rangeUpdate ( rt<< 1 ,l ,r,val ); if ( r > mid ) rangeUpdate ( rt << 1 |1 , l, r,val ); } } int Query (int rt , int l ,int r) { if ( tree[rt].l == tree[rt].r ) return tree[rt].date; else if ( tree[rt].l > r || tree[rt].r < l ) return -1 ; else { push_down (rt,tree[rt].l,tree[rt].r); int mid = ( tree[rt].l + tree[rt].r ) >> 1 ; if ( l <= mid ) return Query ( rt << 1 , l ,r ); else return Query ( rt <<1 |1 ,l,r ); } } int ans () { int las = -1 ; for (int i = 1 ; i <= maxn ; i++ ) { int no = Query (1 ,i,i); if ( no == las) { continue ; } else if ( no != las) { cnt[no]++; } las = no; } for (int i = 0 ; i <= 8000 ; i++){ if ( cnt[i] != 0 ) { cout << i << ' ' << cnt[i] <<endl; } } cout << endl; } int main () { ios::sync_with_stdio (false ); cin.tie (0 ); int n; while (cin >> n) { maxn = 0 ; memset (cnt,0 ,sizeof cnt); int x,y,z; build (1 ,1 ,8001 ); for (int i = 0 ; i <n ; i++){ cin >>x >> y >> z; maxn = max (x,max (y,maxn) ); rangeUpdate (1 ,x+1 ,y,z); } ans (); } return 0 ; }
3.4.3____扫描线
3.4.5____最大连续区间( 与区间最大子段和差不多 )
Tunnel Warfare
题意:
1 - n 单点删除( 可以逐渐修复,从最后一个被删除的点开始修复 ), 问包含该点的最大连续子区间有多大
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 #include <bits/stdc++.h> using namespace std;int n,m;int main () { ios::sync_with_stdio (false ); cin.tie (0 ); while ( cin >> n ) { cin >> m; set<int > s; stack<int > q; s.insert (0 ); s.insert (n + 1 ); for (int i = 1 ; i <= m ; i++){ char cmd; int x; cin >>cmd; if ( cmd == 'Q' ) { if ( s.empty () ) cout << n <<endl; else { cin >> x; if ( s.find ( x ) != s.end () ) cout << 0 <<endl; else cout << *( s.upper_bound (x) ) - *(--s.lower_bound (x) ) - 1 <<endl; } } else if (cmd == 'D' ) { cin >> x; s.insert (x); q.push (x); } else { s.erase ( q.top () ); q.pop (); } } } return 0 ; }
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 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 #include <bits/stdc++.h> using namespace std;const int N = 50010 ;struct Node { int lmax; int rmax; int ans; int l,r; }tree[N*4 ]; void push_up (int rt) { int l = tree[rt].l ,r = tree[rt].r; int mid = (l + r) >> 1 ; tree[rt].lmax = tree[rt << 1 ].lmax + ( tree[rt << 1 ].lmax == mid - l + 1 ? tree[rt << 1 |1 ].lmax : 0 ); tree[rt].rmax = tree[rt << 1 |1 ].rmax + ( tree[rt << 1 |1 ].rmax == r - mid ? tree[rt << 1 ].rmax : 0 ); tree[rt].ans = max ( max ( tree[rt>>1 ].ans , tree[rt >> 1 |1 ].ans ) , tree[rt <<1 ].ans + tree[rt << 1 |1 ].ans ); } void build (int rt ,int l, int r) { tree[rt].l = l , tree[rt].r = r; if (l == r) { tree[rt].ans = tree[rt].lmax = tree[rt].rmax = 1 ; return ; } int mid = (l + r) >> 1 ; build ( rt << 1 , l ,mid ); build ( rt << 1 |1 ,mid + 1 ,r ); push_up ( rt ); } void Update (int rt, int pos,int val) { if ( tree[rt].l == tree[rt].r && tree[rt].l == pos ) { tree[rt].ans = tree[rt].lmax = tree[rt].rmax = val; return ; } int mid = (tree[rt].l + tree[rt].r) >> 1 ; if ( pos <= mid ) Update ( rt << 1 ,pos,val ); else Update ( rt << 1 |1 ,pos,val ); push_up (rt); } int Query (int rt , int x) { if ( tree[rt].ans == 0 || tree[rt].l == tree[rt].r ) { return tree[rt].ans; } int mid = ( tree[rt].l + tree[rt].r ) >> 1 ; if ( x <= mid ) { if ( mid - x + 1 <= tree[rt << 1 ].rmax ) return tree[rt << 1 ].rmax + tree[rt << 1 |1 ].lmax; else return Query ( rt << 1 ,x ); } else { if ( x - mid <= tree[rt << 1 |1 ].lmax ) return tree[rt << 1 ].rmax + tree[rt << 1 |1 ].lmax; else Query ( rt << 1 |1 ,x ); } } int main () { ios::sync_with_stdio (false ); cin.tie (0 ); int n,m; while (cin >> n) { cin >> m; stack<int > s; build (1 ,1 ,n); for (int i = 1 ; i <= m ; i++) { char cmd; int x; cin >> cmd; if ( cmd == 'Q' ) { cin >> x; cout << Query ( 1 , x ) << endl; } else if ( cmd == 'D' ) { cin >> x; s.push (x); Update (1 ,x,0 ); } else { if ( !s.empty () ) { Update ( 1 ,s.top (),1 ); s.pop (); } } } } return 0 ; }
3.4.6____DFS序建树
定义
dfs序是指:每个节点在dfs深度优先遍历中的进出栈的时间序列
对于此树的DFS序就是:
我们发现,一个点的进出栈的时间点之间的时间段就是它的子树在栈中的所有时间。
也就是说,子树的dfs序肯定大于根节点的进栈时间小于根节点的出栈时间,这就成了一个区间问题。所以我们就把一个树上的问题“拍”到了一个线性的数据结构上面。区间问题就是贼好做的了,有各种强大的数据结构可以用来维护区间,例如线段树。
3.4.7____动态开点
例题1 ( cf ),
3.4.8____二分+线段树
列1
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 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 #include <bits/stdc++.h> using namespace std;const int N = 5e5 + 10 ;struct Node { int l,r; int sum,lazy; }t[N << 2 ]; int L,R;void push_up (int rt) { t[rt].sum = t[rt << 1 ].sum + t[rt << 1 |1 ].sum; }void build (int rt,int l, int r) { t[rt] = {l,r,1 ,-1 }; if ( l == r ) return ; int mid = (l + r) >> 1 ; build (rt << 1 ,l,mid),build (rt<<1 |1 ,mid +1 ,r); push_up (rt); } void push_down (int rt) { if ( t[rt].lazy == -1 ) return ; int lazy = t[rt].lazy ,l = t[rt].l,r = t[rt].r; int mid = l + r >> 1 ; t[rt<<1 ].sum = lazy * (mid - l +1 ); t[rt<<1 |1 ].sum = lazy * (r - mid); t[rt << 1 |1 ].lazy = t[rt << 1 ].lazy = lazy; t[rt].lazy = -1 ; } int findLeft (int rt) { if ( t[rt].l == t[rt].r ) return t[rt].r; push_down (rt); if ( t[rt << 1 ].sum != 0 ) findLeft (rt << 1 ); else findLeft (rt << 1 |1 ); } int findRight (int rt) { if ( t[rt].l == t[rt].r ) return t[rt].l; push_down (rt); if ( t[rt << 1 |1 ].sum != 0 ) findRight (rt <<1 |1 ); else findRight (rt << 1 ); } void rangeUpdate (int rt,int l,int r,int &val) { if ( val == 0 || t[rt].sum == 0 ) return ; if ( l <= t[rt].l && r >= t[rt].r && t[rt].sum <= val ) { val -= t[rt].sum; L = min (L,findLeft (rt)) , R = max (R,findRight (rt)); t[rt].sum = 0 ; t[rt].lazy = 0 ; return ; } int mid = t[rt].l + t[rt].r >> 1 ; push_down (rt); if ( l <= mid ) rangeUpdate (rt << 1 ,l,r,val); if ( r > mid ) rangeUpdate (rt << 1 |1 ,l,r,val); push_up (rt); } int rangeDelte (int rt,int l,int r) { if ( l <= t[rt].l && r >= t[rt].r ) { int res = t[rt].r - t[rt].l + 1 - t[rt].sum; t[rt].sum = t[rt].r - t[rt].l + 1 ; t[rt].lazy = 1 ; return res; } int mid = (t[rt].l + t[rt].r) >> 1 ; int res = 0 ; push_down (rt); if ( l <= mid ) res += rangeDelte (rt << 1 ,l,r); if ( r > mid ) res += rangeDelte (rt << 1 |1 ,l,r); push_up (rt); return res; } int main () { ios::sync_with_stdio (false ); cin.tie (0 ); int n,t,m,x,y,cmd,te; cin >> t; while (t--) { cin >> n >> m; build (1 ,1 ,n); while (m--) { cin >> cmd >> x >> y; if (cmd == 1 ) { L = n + 1 , R = 0 ,te = y; rangeUpdate (1 ,x+ 1 ,n,y); if (te == y) cout << "Can not put any one." <<endl; else cout << L - 1 << ' ' <<R - 1 <<endl; } else cout << rangeDelte (1 ,x + 1 ,y + 1 ) << endl; } cout <<endl; } return 0 ; }
3.5 ____树状数组
3.6 ____堆
什么是前向星? 前向星是一种特殊的边集数组,我们把边集数组中的每一条边按照起点从小到大排序,如果起点相同就按照终点从小到大排序
用len[i]表示,所有以i为起点的边的数量。
用head[i]表示,以i为起点的边集在排序后第一次出现的位置。
以下图为例,我们建立前向星表
我们输入边的顺序为:
1 2 2 3 3 4 1 3 4 1 1 5 4 5
那么排完序后就得到: 排序后编号 : 1 2 3 4 5 6 7起点u : 1 1 1 2 3 4 4 终点v : 2 3 5 3 4 1 5
得到:
head[1] = 1 len[1] = 3 head[2] = 4 len[2] = 1 head[3] = 5 len[3] = 1 head[4] = 6 len[4] = 2
链式前向星数据结构定义 以及加边函数 为
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 <bits/stdc++.h> using namespace std;const int N = 1e5 +10 ;struct Egde { int ne,to,w; }e[N]; int h[N],idx = 0 ;void add (int a,int b,int w) { e[idx].w = w; e[idx].to = b; e[idx].ne = h[a]; h[a] = idx++; } int main () { ios::sync_with_stdio (false ); cin.tie (0 ); memset (h,-1 ,sizeof h); int n,m; cin >> n >> m; for (int i = 0 ; i < n ; i++){ int a,b,w; cin >> a>> b >>w; add (a,b,w); } for (int i = 1 ; i <= n ; i++){ for ( int j = h[i] ; j != -1 ; j = e[j].ne){ cout << i << '-' << e[j].to << '-' << e[j].w<<endl; } } return 0 ; }
可以试试手动写一遍这个代码,我在学习的时候,写到next之后我就突然明白了这个结构了
next表示对于顶点a的链路,最后一个节点的位置 to表示这表边的终点 w表示这条边的边权 head表示,顶点a最后加上的边的编号
head[]数组一般初始化为-1
现在我们还是按照上面的图和输入来模拟一下:
edge[0].to = 2; edge[0].next = -1; head[1] = 0;
edge[1].to = 3; edge[1].next = -1; head[2] = 1;
edge[2].to = 4; edge[2],next = -1; head[3] = 2;
edge[3].to = 3; edge[3].next = 0; head[1] = 3;
edge[4].to = 1; edge[4].next = -1; head[4] = 4;
edge[5].to = 5; edge[5].next = 3; head[1] = 5;
edge[6].to = 5; edge[6].next = 4; head[4] = 6;
3.8 ____可持续化线段树(主席树)3.8.1____权值线段树 权值线段树 之所以带上”权值”二字,是因为它是记录权值的线段树。所以在某些题中,需要离散化处理输出数据。记录权值指的是,每个点上存的是区间内的数字出现的总次数。比如一个长度为10的数组[1,1,2,3,3,4,4,4,4,5]。
1出现了两次所以节点[1,1]的权值为2
2出现了一次所以节点[2,2]的取值为1
因为1,2共出现三次所以节点[1,2]的权值为3
因为这个测试数据比较少,没有对数据进行离散化,所有会有权值为0的节点出现
应用求第K大(或小)数
我们向树中插入4个数 $ [1,2,5,7]$ ,得到的权值线段树如下
现在我们从根节点开始,向下去找第3(K)小的数(rt为当前节点的数组编号)
我们的判断准则为: 我们求的是区间 [1,7]的第3小数,首先我们要去看这个节点的左子树的取值为多少,如果K <= t[rt<<1].权值
,那么我们的问题就缩小为了,找左儿子所在的区间内的第K小数;反之如果 K > t[rt<<1].权值
,那么说明[1,7]的第K小数在他的右子树上,那么问题就变为了,找右儿子所在区间内的第K-t[rt<<1].权值
小的数。
运用这个原理,第一层我们就把问题缩小为,求[5,7]区间第1小数,然后进第二层问题缩小为求[5,6]区间的第1小数,进入第三层我们就能找到我们的答案了,为5,这就是权值线段树求第K小(大)数的原理。
下面我们来看一道板子题
B-简易版第k大_2019长沙学院暑假集训队第二次校赛
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 75 76 77 78 79 80 #include <bits/stdc++.h> using namespace std;const int N = 1e6 + 10 ;struct node { int l,r; int cnt; }t[N<<2 ]; int a[N];void build (int rt,int l ,int r) { t[rt].l = l,t[rt].r = r; if (l == r){ t[rt].cnt = 0 ; return ; } int mid = l + r >>1 ; build (rt <<1 ,l,mid),build (rt<<1 |1 ,mid+1 ,r); } void update (int rt,int loc,int val) { if ( t[rt].l == t[rt].r ){ t[rt].cnt += val; return ; } int mid = t[rt].l + t[rt].r >> 1 ; if ( loc <= mid ) update (rt<<1 ,loc,val); else update (rt<<1 |1 ,loc,val); t[rt].cnt = t[rt<<1 ].cnt + t[rt<<1 |1 ].cnt; } int query (int rt,int k) { if (t[rt].l == t[rt].r){ return t[rt].l; } int mid = t[rt].l + t[rt].r >> 1 ; if ( k <= t[rt<<1 ].cnt ) return query (rt<<1 ,k); else return query (rt<<1 |1 ,k - t[rt<<1 ].cnt); } int main () { ios::sync_with_stdio (false ); cin.tie (0 ); cout.tie (0 ); int n,m; cin >>n >> m; build (1 ,1 ,1000002 ); for (int i = 1 ; i <= n ; i++){ cin >> a[i]; update (1 ,a[i],1 ); } for (int i = 0 ; i < m ; i++){ int op,x,y; cin >> op; if (op == 1 ){ cin >>x; cout << query (1 ,x) <<endl; } else { cin >>x>> y; if ( a[x] == y ) continue ; update (1 ,a[x],-1 ); update (1 ,y,1 ); a[x] = y; } } return 0 ; }
3.8.2____主席树(可持续化线段树) 在知道了权值线段树是什么,那么我们就可以开始认识什么是主席树了。主席树是一棵可持久化线段树,可持久化指的是它保存了这棵树的所有历史版本 。
最简单的办法是:如果你输入了n个数,那么每输入一个数字a[i],就构造一棵保存了从a[1]到a[i]的权值线段树。之所以这么做,是因为我们可以把第j棵树和第(i-1)棵树上的每个点的权值相减,来得到一颗新的权值线段树,而这个新的树相当于是除去a[i]之前的数,将第一个输入的数变为a[i+1],最后一个输入的数为a[i]的权值线段树了,那么求区间K小值,就变成上面权值线段树的内容了。
如果这么说不太好理解的话,我们可以思考另外一个模型:求数组a[1]到a[n]的和 。如果只是求[1,n]这一段的和,那么我们直接全部加起来就可以了,或者求一个前缀和sum[n]即可。那么如果我给定了l和r,想要知道[l,r]这段区间上的和呢?是不是利用前缀和sum[r]-sum[l-1]就可以轻松得到?那么主席树的思想也是如此 ,将tree[r]-tree[l-1]得到的一棵权值线段树即为属于[l,r]的一棵权值线段树,那么在这么一棵权值线段树上求第k大不是就转变为之前的问题了么。
还有一个问题需要解决,那就是空间问题 。
显而易见的是,如果每输入一个数就重新构造一棵权值线段树,必然会导致空间不够用:一棵线段树的空间就是n * 4,那么一共的空间开销就是n * n * 4,显然是会MLE的。那么这个问题怎么解决呢?
可以发现每更新一个点,就会从它开始把它的所有祖先都更新一次,而其他的点都没有被改变,即:每次改变的结点只有$log_n$个 。这样,我们每次输入一个数,只需要多开logn个空间,那么实际的空间开销只有$n*(4+log_n)$,满足了空间要求。
板子题255. 第K小数 - AcWing题库
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 75 76 77 78 79 #include <bits/stdc++.h> using namespace std;const int N =100005 ;int n,m,len,a[N],d[N];int T[N],idx;struct node { int l,r,val; }t[4 *N + N*17 ]; int build (int l,int r) { int p = ++idx , mid = l + r >> 1 ; if (l < r){ t[p].l = build (l,mid); t[p].r = build (mid+1 ,r); } t[p].val = 0 ; return p; } int update (int pre,int l ,int r,int val) { int p = ++idx,mid = l + r >>1 ; t[p].l = t[pre].l , t[p].r = t[pre].r , t[p].val = t[pre].val + 1 ; if (l < r){ if ( val <= mid ) t[p].l = update (t[pre].l , l ,mid ,val); else t[p].r = update (t[pre].r,mid+1 ,r,val); } return p; } int query (int x,int y,int l,int r,int k) { if (l == r) return l; int sum = t[ t[y].l ].val - t[ t[x].l ].val, mid = l + r >> 1 ; if ( k <= sum ) return query ( t[x].l,t[y].l,l,mid,k ); else return query ( t[x].r,t[y].r,mid + 1 ,r ,k - sum ); } int main () { ios::sync_with_stdio (false ); cin.tie (0 ); cout.tie (0 ); cin >> n >> m; for (int i = 1 ; i <= n ; i++){ cin >> a[i]; d[i] = a[i]; } sort (d+1 ,d+n+1 ); len = unique (d+1 ,d+1 +n) - (d+1 ); for (int i = 1 ; i <= n ; i++){ a[i] = lower_bound (d+1 ,d+1 +len,a[i]) - d; } T[0 ] = build (1 ,len); for (int i = 1 ; i <= n ; i++) T[i] = update (T[i-1 ],1 ,len,a[i]); while (m--){ int l,r,k; cin >> l >> r >>k; int ans = query (T[l-1 ],T[r],1 ,len,k); cout << d[ans] <<endl; } return 0 ; }
3.9____可持续化字典树
3.10____栈
3.10.1____表达式求值
(中缀表达式为运算树的中序遍历)
![中缀表达式](D:\workProgram\Typora_MarkDown\Hoppz算法笔记 11e225234f9f4382825a35d69f9cb19f\中缀表达式.png)
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 #include <bits/stdc++.h> using namespace std;stack<int > num; stack<int > op; unordered_map<char ,int > pr{{'+' ,1 }, {'-' ,1 },{ '*' ,2 },{'/' ,2 } }; void eval () { auto b = num.top ();num.pop (); auto a = num.top ();num.pop (); auto c = op.top ();op.pop (); int ans; if ( c == '+' ) ans = a + b; else if ( c == '-' ) ans = a - b; else if ( c == '*' ) ans = a * b; else if ( c == '/' ) ans = a / b; num.push (ans); } int main () { ios::sync_with_stdio (false ); cin.tie (0 ); string str; cin >> str; for (int i = 0 ; i < str.length () ; i++) { auto c = str[i]; if ( isdigit (c) ) { int x = 0 ,j = i; while ( j < str.size () && isdigit ( str[j] ) ) x = x * 10 + (int )( str[j ++] - '0' ); i = j - 1 ; num.push (x); } else if ( c == '(' ) op.push (c); else if ( c == ')' ) { while ( op.top () != '(' ) eval (); op.pop (); } else { while ( op.size () && pr[op.top ()] >= pr[c] ) eval (); op.push (c); } } while ( op.size () ) eval (); cout << num.top () << endl; return 0 ; }
3.11____分块思想(数列分块)
例
4____动态规划
取自Acwing分享
4.1 ____背包问题
4.1.1 ____01背包问题——n个东西里选任意个出来
初探dp,我觉得最重要的还是状态转移
开始听了y总
的视频课,讲真是没听懂,之后下午就一直看《挑战》
(讲真是本好书),看到书上有这样的矩阵格子,我就一步一步顺着代码,算几个数据,然后突然!我悟了!最重要的就是那几行状态转移。
01背包矩阵
外面的两层for循环,枚举每一种状态
对于dp[ i ][ j ]的解释——二维只是方便理解,真正的
状态转移方程 f[i][j] = max(f[i-1][j], f[i-1][j-v[i]]+w[i])
f[i-1][j]
: 不选 第i个物品的集合中的最大值f[i-1][j-v[i]] + w[i]
: 选 第i个物品的集合,但是直接求不容易求所在集合的属性,这里迂回打击一下,先将第i个物品的体积减去,求剩下集合中选法的最大值.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 const int maxn = 1010 ;int n,m;int v[maxn],w[maxn];int dp[maxn][maxn];int main () { cin >> n >> m; for (int i = 1 ; i <= n ; i++){ cin >> v[i] >> w[i]; } for (int i = 1 ; i <= n ;i ++){ for (int j = 0 ; j <= m; j ++){ **dp[i][j] = dp[i - 1 ][j];** if ( j >= v[i] ) **dp[i][j] = max (dp[i][j],dp[i - 1 ][j - v[i]] + w[i]);** } } cout << dp[n][m] <<endl; return 0 ; }
优化版,一维dp[maxn]
f[i] 仅用到了f[i-1]层
j与j-v[i] 均小于j
若用到上一层的状态时,从大到小枚举, 反之从小到大哦
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 const int N = 1010 ;int n, m;int v[N], w[N];int f[N];int main () { cin >> n >> m; for (int i = 1 ; i <= n; i++) cin >> v[i] >> w[i]; for (int i = 1 ; i <= n; i++) for (int j = m; j >= v[i]; j--) **f[j] = max (f[j], f[j-v[i]]+w[i]);** cout << f[m] << endl; return 0 ; }
在谈01背包——质数分解
在19年的蓝桥杯决赛,有这个题
外层for循环,遍历的是要放进背包的东西
内层for循环,是限制的条件
比如在01背包的题中 要遍历的东西是n个物品
而限制条件是,背包的容量
在质数分解的题中要遍历的是打表出来的质数
而限制条件是可以被加的最大值2019
4.1.2 ___完全背包问题
完全背包问题与01背包问题,题目唯一的不同就是单个物品,可选的次数不限。
所以 由上式子
,我们可以看出当取到最大值
d[i][j - 1]
与dp[i -1][j - v] + w .........
只相差一个 w 所以可得下式:
二维dp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 const int maxn = 1010 ;int n,m;int w[maxn],v[maxn];int dp[maxn][maxn];int main () { cin >> n >> m; for (int i = 1 ; i <= n; i++){ cin >> v[i] >> w[i]; } for (int i = 1 ; i <= n; i++){ for (int j = 0 ; j <= m; j++){ dp[i][j] = dp[i - 1 ][j]; if ( j >= v[i] ) dp[i][j] = max ( dp[i][j], dp[i][ j - v[i] ] + w[i] ); } } cout << dp[n][m] << endl; return 0 ; }
一维dp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 const int maxn = 1010 ;int n,m;int w[maxn],v[maxn];int dp[maxn];int main () { cin >> n >> m; for (int i = 1 ; i <= n; i ++){ cin >> v[i] >> w[i]; } for (int i = 1 ; i <= n; i++){ for (int j = v[i]; j <= m ; j++){ dp[j] = max (dp[j] , dp[j - v[i]] + w[i]); } } cout << dp[m]; return 0 ; }
4.1.3 ____多重背包问题
数据小于100的暴力解法————将物品拆成s个放入数据中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 const int maxn = 1010 ;int n,m;int dp[maxn];int v[maxn],w[maxn],s[maxn];int main () { cin >> n >> m ; for (int i = 1 ; i <= n; i++){ cin >> v[i] >> w[i] >> s[i]; } for (int i = 1 ; i <= n; i++ ){ for (int j = m; j >= 0 ; j--){ ** for (int k = 0 ; k <= s[i]; k++ ){ if ( j >= k*v[i] ){ dp[j] = max (dp[j],dp[j - k*v[i]] + k*w[i] ); } } } } cout << dp[m] << endl; return 0 ; }
数据小于1000————二进制
4.2____最长上升子序列
子序列的定义
对于数列E = { 3 1 2 1 8 5 6 },{ 1 2 1}是E的子序列,{ 3 2 8 6}也是E的子序列
E最长上升子序列为 { 1 2 5 6 };
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 int a[1010 ];int dp[1010 ]; int main () { ios::sync_with_stdio (false ); cin.tie (0 ); int n,ans=0 ; cin >> n; for (int i = 0 ; i< n ; i++) cin >> a[i]; fill (dp,dp+n,1 ); for (int i = 1 ; i < n ; i ++){ for (int j = 0 ; j < i ; j++){ if ( a[i] > a[j] ) dp[i] = max (dp[j] +asa 1 ,dp[i]); } ans = max (ans,dp[i]); } cout <<ans <<endl; return 0 ; }
二分求解( O( nlogn ) )
记录每个长度序列的最小末尾数字 ,二分求解
最终代码 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 <bits/stdc++.h> using namespace std;typedef pair<int ,int > pir;int a[1010 ];int dp[1010 ]; int n,cnt;int main () { ios::sync_with_stdio (false ); cin.tie (0 ); cin >> n; for (int i = 1 ; i <= n ; i++){ cin >> a[i]; } dp[cnt++] = a[1 ]; for (int i = 1 ; i <= n ; i ++){ if ( a[i] > dp[cnt - 1 ] ) dp[cnt ++] = a[i]; else { int l = 0 , r = cnt-1 ; while (l < r){ int mid = l + r >> 1 ; if ( dp[mid] < a[i]) l = mid + 1 ; else r = mid; } if (a[i] < dp[r] )dp[r] = a[i]; } } cout << cnt << endl; return 0 ; }
4.3____最长公共子序列 给两个串,求既是A的子序列,又是B的子序列的序列的最大长度
忘了在去acwing看嘛,不看视频
,直接看别人的文字更好看懂
$ O(n^2) $ 做法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 int n,m;char a[1010 ];char b[1010 ];int dp[1010 ][1010 ];int main () { ios::sync_with_stdio (false ); cin.tie (0 ); cin >> n >> m >> a+1 >> b+1 ; for (int i = 1 ; i <= n ; i++) for (int j = 1 ; j <= m ; j++){ if ( a[i] == b[j] ) dp[i][j] = dp[i - 1 ][j- 1 ] + 1 ; else dp[i][j] = max ( dp[i -1 ][j],dp[i][j-1 ] ); } cout << dp[n][m]<<endl; return 0 ; }
5____图论
5.1图上搜索
5.5 ____拓扑排序
定义
若一个由图中所有点构成的序列A满足:对于图中的每条边(x,y),x在A中都出现在y之前,则称A是该图的一个拓扑序列
只有有向图才有拓扑序列
图中存在环无法构成拓扑序列
可证明,有向无环图就可构成拓扑序列(有向无环图简称拓扑图)
入度为零的点都可以为序列的起始点(所以第一步就是把所有入度为零的点入队)
5.6 ____Dijkstra
Dijkstra图中不能有负权边
每次都找最短的那个点(贪心的思想)
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 const int maxn = 5e2 +10 ;int n,m;int dist[maxn];int g[maxn][maxn];bool st[maxn];int Dijkstra () { memset (dist,0x3f ,sizeof (dist)); dist[1 ] = 0 ; for (int i = 0 ; i < n; i++){ int t = -1 ; for (int j = 1 ; j <= n; j ++) if (!st[j] && (t == -1 || dist[t] > dist[j]) ){ t = j; } st[t] = true ; for (int j = 1 ; j <= n; j++){ dist[j] = min (dist[j],dist[t] +g[t][j]); } } if (dist[n] == 0x3f3f3f3f ) return -1 ; return dist[n]; } int main () { cin >> n >>m; memset (g,0x3f ,sizeof (g)); while (m -- ){ int x, y ,z; cin >> x >> y >>z; g[x][y] = min (g[x][y],z); } cout << Dijkstra () << endl; return 0 ; }
堆优化
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 75 76 77 78 #include <bits/stdc++.h> using namespace std;const int N = 150010 ;typedef pair<int ,int > pir;struct node { int next,w,to; }e[N]; int head[N],idx,dist[N],st[N],te1,te2,dis,n,m;priority_queue<pir,vector<pir>,greater<pir>> heap; void add (int a,int b, int d) { e[idx].w = d; e[idx].next = head[a]; e[idx].to = b; head[a] = idx ++; } int Dijkstra () { memset (dist,0x3f3f3f3f ,sizeof dist); dist[1 ] = 0 ; heap.push ({0 ,1 }); while (heap.size ()) { auto te = heap.top (); heap.pop (); int newp = te.second,tedis = te.first; if ( st[newp] ) continue ; st[newp] = true ; for (int i = head[newp] ; i != -1 ; i = e[i].next) { int j = e[i].to; if ( dist[j] > tedis + e[i].w ){ dist[j] = tedis + e[i].w; heap.push ({dist[j],j}); } } } if ( dist[n] == 0x3f3f3f3f ) return -1 ; else return dist[n]; } int main () { ios::sync_with_stdio (false ); cin.tie (0 ); memset (head,-1 ,sizeof head); cin >>n >> m; for (int i= 0 ; i < m ; i++){ cin >> te1 >> te2 >> dis; add (te1,te2,dis); } cout << Dijkstra () << endl; return 0 ; }
第k短路径
https://blog.csdn.net/metaphysis/article/details/106230224?ops_request_misc=%7B%22request%5Fid%22%3A%22160546503219724836724504%22%2C%22scm%22%3A%2220140713.130102334.pc%5Fall.%22%7D&request_id=160546503219724836724504&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allfirst_rank_v2~rank_v28-1-106230224.pc_search_result_cache&utm_term=第二短路径&spm=1018.2118.3001.4449
5.7 ___spfa
Bellman-Ford的优化
处理不含负环的图,能判断是否图中存在负环
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 75 76 77 78 79 typedef pair<int , int > PII;const int N = 100010 ;int n,m; int h[N], w[N], e[N], ne[N], idx; int dist[N]; int cnt[N];bool st[N]; bool spfa () { memset (dist,0 ,sizeof (dist)); memset (cnt,0 ,sizeof (cnt)); memset (st,false ,sizeof (st)); dist[1 ] = 0 ; queue<int > q; q.push (1 ); for (int i = 2 ; i <= n ; i++ ){ q.push (i); st[i] = true ; } while (!q.empty ()){ int t = q.front (); q.pop (); st[t] = false ; cnt[t] = cnt[t] + 1 ; if (cnt[t] >= n) return true ; for (int i = h[t] ; i != -1 ;i = ne[i]){ int j = e[i]; if (dist[j] > dist[t] + w[i]){ dist[j] = dist[t] + w[i]; if (!st[j]){ q.push (j); st[j] = true ; } } } } return false ; } void add (int a,int b ,int c) { e[idx] = b; w[idx] = c; ne[idx] = h[a]; h[a] = idx++; } int main () { cin >> n >> m; memset (h,-1 ,sizeof h); while (m --){ int a,b,c; cin >> a >> b >> c; add (a,b,c); } bool t = spfa (); if ( t )cout << "Yes" <<endl; else cout << "No" << endl; return 0 ; }
5.8 ____Bellman-ford
无负权回路 ——> -∞
有遍数限制的最短路径,就用bellman,其他用spfa
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 #include <bits/stdc++.h> using namespace std;const int N = 510 ,M =10010 ;int n ,m,k;int dist[N],backup[N];struct edge { int a,b,w; }edge[M]; int bellman_ford () { memset (dist,0x3f ,sizeof dist); dist[1 ] = 0 ; for (int i = 0 ; i < k ; i++){ memcpy (backup,dist,sizeof dist); for (int j = 0 ; j < m ; j++){ int a = edge[j].a; int b = edge[j].b; int w = edge[j].w; dist[b] = min (dist[b],backup[a] + w); } } if (dist[n] > 0x3f3f3f3f / 2 ) return -1 ; else return dist[n]; } int main () { ios::sync_with_stdio (false ); cin.tie (0 ); cin >> n >> m >> k; for (int i = 0 ; i < m; i ++){ int a,b,w; cin >> a >> b >> w; edge[i] = {a,b,w}; } int t = bellman_ford (); if (t == -1 ) cout << "impossible" << endl; else cout << t << endl; return 0 ; }
Bellman-Ford 与 Dijkstra 的区别
B算法中,是逐遍遍历,在源点联通这个点之前,是没办法更新的,当然可能因为更新了之后再一步的操作中会走很多步,所以需要back数组
D算法中,是按最近的点来找,先找到距离源点最近的点,然后再按照这个点来遍历其他边
5.9 ____Floyd
dp思路做
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 const int N = 210 , M = 2e+10 , INF = 1e9 ;int n, m, k, x, y, z;int d[N][N];void floyd () { for (int k = 1 ; k <= n; k++) for (int i = 1 ; i <= n; i++) for (int j = 1 ; j <= n; j++) d[i][j] = min (d[i][j], d[i][k] + d[k][j]); } int main () { cin >> n >> m >> k; for (int i = 1 ; i <= n; i++) for (int j = 1 ; j <= n; j++) if (i == j) d[i][j] = 0 ; else d[i][j] = INF; while (m--) { cin >> x >> y >> z; d[x][y] = min (d[x][y], z); } floyd (); while (k--) { cin >> x >> y; if (d[x][y] > INF/2 ) puts ("impossible" ); else cout << d[x][y] << endl; } return 0 ; }
路径最长边最小化
dp[i][j] = min ( dp[i][j] , max(dp[i][k] , dp[k][j] ) );
5.10 ____Prim
5.11 ____Kruskal
5.12 ____最小生成树
5.13 ____负环
5.14 ____差分约束
5.15 ____有向图的强连通分量
5.16 ____无向图的双连通分量
5.17 ____二分图
5.18 ____拓扑排序
有向无环图,DAG必有拓扑序列
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 bool topsort () { int hh = 0 , tt = -1 ; for (int i = 1 ; i <= n; i ++ ) if (!d[i]) q[ ++ tt] = i; while (hh <= tt) { int t = q[hh ++ ]; for (int i = h[t]; i != -1 ; i = ne[i]) { int j = e[i]; if (-- d[j] == 0 ) q[ ++ tt] = j; } } return tt == n - 1 ; } 作者:yxc 链接:https: 来源:AcWing 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
6 ____数论
数论基础知识点
6.1 ____中国剩余定理
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 ll exgcd (ll a, ll b, ll &x, ll &y) { if (b == 0 ){ x = 1 ; y = 0 ; return a; } ll gcd = exgcd (b, a % b, y, x); y -= a / b * x; return gcd; } ll mod (ll a, ll b) { return (a % b + b) % b; } int main () { int n; cin >> n; ll a1, m1; cin >> a1 >> m1; int flag = 1 ; ll k1, k2; while (n -- ){ ll a2, m2; cin >> a2 >> m2; ll d = exgcd (a1, a2, k1, k2); if ((m2 - m1) % d){ flag = 0 ; } if (flag){ k1 = mod (k1 * (m2 - m1) / d, a2 / d); m1 = k1 * a1 + m1; a1 = a1 / d * a2; } } if (flag){ cout << m1 << endl; } else { cout << "-1" << endl; } return 0 ; }
6.2 ____欧拉函数线性筛
筛法求欧拉函数
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 const int N = 1e6 + 10 ;int p[N], phi[N];bool st[N];int cnt = 0 ;void ol () { for (int i = 2 ; i <= N; i ++ ){ if (!st[i]){ p[cnt ++] = i; phi[i] = i - 1 ; }WW for (int j = 0 ; p[j] <= N / i; j ++ ) { st[p[j] * i] = true ; if (i % p[j] == 0 ){ phi[p[j] * i] = phi[i] * p[j]; break ; } phi[p[j] * i] = phi[i] * (p[j] - 1 ); } } } int main () { int n; cin >> n; ol (); ll ans = 1 ; for (int i = 2 ; i <= n; i ++){ ans += phi[i]; } cout << ans << endl; return 0 ;
6.3 ____扩展欧几里得算法
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 ll exgcd (ll a, ll b, ll &x, ll &y) { if (b == 0 ){ y = 0 ; x = 1 ; return a; } ll gcd = exgcd (b, a % b, y, x); y -= a / b * x; return gcd; } int main () { int t; cin >> t; while (t -- ){ ll a, b, x, y; cin >> a >> b; exgcd (a, b, x, y); cout << x << " " << y << endl; } return 0 ; }
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 bool topsort () { int hh = 0 , tt = -1 ; for (int i = 1 ; i <= n; i ++ ) if (!d[i]) q[ ++ tt] = i; while (hh <= tt) { int t = q[hh ++ ]; for (int i = h[t]; i != -1 ; i = ne[i]) { int j = e[i]; if (-- d[j] == 0 ) q[ ++ tt] = j; } } return tt == n - 1 ; }
6.4____分解质因数
试除法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 void get_prime (int n) { for (int i = 2 ; i <= n / i; i++){ int cnt = 0 ; if (n % i == 0 ) { while (n %i == 0 ){ n /= i ; cnt ++; } cout << i <<" " << cnt << endl ; } } if (n>1 ) cout<<n<<' ' <<1 <<endl; cout <<endl; }
6.5 ____求素数
快速判断一个数是不是素数
1 2 3 4 5 6 7 8 9 10 11 bool is_p ( int num ) { if (num ==2 || num==3 ) return 1 ; if ( (num %6 != 1 &&num %6 != 5 ) || num == 1 ) return 0 ; for (int i= 5 ; i <= num / i; i+=6 ) if (num %i== 0 ||num %(i+ 2 )==0 ) return 0 ; return 1 ; }
朴素筛法-O(nlogn)
st[N]判断是否为素数,prime储存筛出来的质数
1 2 3 4 5 6 7 8 9 int prime[N], cnt;bool st[N];void get_primes (int n) { for (int i = 2 ; i <= n; i++) { if (!st[i]) prime[cnt++] = i; for (int j = i+i; j <= n; j += i) st[j] = true ; } }
埃氏筛法-O(nloglogn)
1 2 3 4 5 6 7 8 9 void get_primes (int n) { for (int i = 2 ; i <= n; i++) { if (!st[i]){ prime[cnt++] = i; for (int j = i; j <= n; j += i) st[j] = true ; } } }
线性筛-O(n)
算法核心:x仅会被其最小质因子筛去
1 2 3 4 5 6 7 8 9 10 void get_prime (int x) { for (int i = 2 ; i <= x; i++) { if (!st[i]) prime[cnt++] = i; for (int j = 0 ; prime[j] <= x / i; j++) { st[prime[j]*i] = true ; if (i % prime[j] == 0 ) break ; } } }
对于任意一个合数x,假设pj为x最小质因子,当i<x/pj时,一定会被筛 1.i%pj == 0, pj一定为i最小质因子,pj也定为pji最小质因子 2.i%pj != 0, pj一定小于i的所有质因子,所以pj也为pj i最小质因子
6.6 ____求约数——通用试除法
约数的定义:整数a 除以 整数b(b≠0) 除得的 商 正好是整数 而没有余数 a称为b的倍数,b称为a的 约数
试除法求一个数的约数( O(n*sqrt(a)) )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 vector<int > get_divisors (int n) { vector<int > res; for (int i = 1 ; i <= n / i; i++){ if (n % i == 0 ){ res.push_back (i); if ( n / i != i ) res.push_back ( n / i); } } sort (res.begin (),res.end ()); return res; }
求约数个数
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 const int mod = 1e9 + 7 ;int main () { int n; unordered_map<int ,int > prime; cin >> n; while (n--){ int temp; cin >> temp; for (int i = 2 ; i <= temp / i ; i++){ while ( temp % i == 0 ){ temp /= i; prime[i]++; } } if (temp > 1 ) prime[temp]++; } long long res = 1 ; for (auto item : prime) res = res * ( item.second + 1 ) % mod ; cout << res << endl; return 0 ; }
求约数和
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 const int mod = 1e9 + 7 ;int main () { int n ; cin >> n; unordered_map<int ,int > prime; while (n --){ int temp; cin >> temp; for (int i = 2 ;i <= temp / i; i++ ) while (temp % i == 0 ){ temp /= i; prime[i] ++; } if (temp > 1 ) prime[temp] ++; } long long res = 1 ; for (auto item : prime){ int p = item.first, a = item.second; long long t = 1 ; while (a--) t = ( t * p + 1 ) % mod; res = t * res % mod; } cout << res << endl; return 0 ; }
对于 while(a--) t = ( t * p + 1 ) % mod;
的解释
6.7 ____欧拉函数
定义与公式
在1- n中与n互质的数的个数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 while (n --){ int temp; cin >> temp; int res = temp; for (int i = 2 ; i <= temp / i ; i++) if ( temp % i == 0 ){ res = res / i * ( i - 1 ); while ( temp % i == 0 ) temp /= i; } if (temp > 1 ) res = res / temp * ( temp - 1 ); cout << res << endl; }
7 ____搜索
7.1 ____Flood Fill算法
其实就是dfs,bfs遍历矩阵,我习惯用dfs的方法
Acwing1106 山峰和山谷
题意:
就是找山峰,山谷,对山峰的定义就是他周围的8连通数都比他小,山谷的定义就是他周围的8连通数都比大
思路:
直接弄先判周围的数是不是都小于(大于),等于他自己然后再进去扫等于的数
写了半天结果Memory Limit Exceeded
看来DFS做这种题容易超内存啊,用于明白为什么都用BFS了!!!
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 #include <bits/stdc++.h> using namespace std;#define x first #define y second const int N = 1010 ;int g[N][N];int st[N][N];int n;void bfs (int x,int y ,bool &hi,bool &lo) { queue< pair<int ,int > > q; q.push ( {x,y} ); st[x][y] = 1 ; while (!q.empty ()) { auto no = q.front (); q.pop (); for (int i = -1 ; i <=1 ; i++){ for (int j = -1 ; j <= 1 ; j ++){ int tx = no.x + i; int ty = no.y + j; if ( i == 0 && j == 0 ) continue ; if ( tx < 0 || ty < 0 || tx >= n || ty >= n ) continue ; if ( g[no.x][no.y] != g[tx][ty] ) { if ( g[no.x][no.y] > g[tx][ty] ) lo = false ; else hi = false ; } else if ( !st[tx][ty] ) { q.push ( {tx,ty} ); st[tx][ty] = 1 ; } } } } } int main () { ios::sync_with_stdio (false ); cin.tie (0 ); cin >> n ; for (int i = 0 ; i < n ; i++) for (int j =0 ; j < n ; j ++) cin >> g[i][j]; int nhi = 0 ,nlo = 0 ; for (int i = 0 ; i < n ; i++) for (int j = 0 ; j < n ; j ++) if (!st[i][j] ) { bool hi = true , lo = true ; bfs (i,j,hi,lo); if ( hi ) nhi ++; if ( lo ) nlo ++; } cout << nhi << ' ' << nlo << endl; return 0 ; }
7.2 ____最小步数模型
Acwing1107 魔板
bfs + 哈希 + 康拓展开
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #include <bits/stdc++.h> using namespace std;const int fnum[] = {1 , 1 , 2 , 6 , 24 , 120 , 720 , 5040 , 40320 , 362880 };int a[10 ];const int maxn = 40400 ; pair<int ,int > pre[maxn]; int st[maxn];struct node { int a[8 ]; int root}; int main () { bfs return 0 ; }
AcWing 845 八数码
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 ``` ## 7.4 ____双向搜索 ### 双向BFS > *双向同时搜索*的基本思路是从状态图上的起点和终点同时开始进行 [广搜](https: > [844. 走迷宫 - AcWing题库](https: ```c++ #include <bits/stdc++.h> using namespace std;struct node { int x,y,cnt,type; }; int st[50 ][50 ];int dis[50 ][50 ];int g[50 ][50 ];int n,m,ans;int dx[] = {0 ,0 ,-1 ,1 },dy[] = {-1 ,1 ,0 ,0 };int bfs (){ queue<node> q; q.push ({0 ,0 ,0 ,1 }); q.push ({n-1 ,m-1 ,0 ,2 }); while (!q.empty ()){ auto no = q.front (); q.pop (); if ( st[no.x][no.y] != 0 && st[no.x][no.y] != no.type ){ return dis[no.x][no.y] + no.cnt + 1 ; } else if ( st[no.x][no.y] != 0 && st[no.x][no.y] == no.type ){ continue ; } else { st[no.x][no.y] = no.type; dis[no.x][no.y] = no.cnt; } for (int i = 0 ; i < 4 ; i++){ int tx = no.x + dx[i],ty = no.y + dy[i]; if ( tx >= 0 && ty >= 0 && tx < n && ty < m && st[tx][ty] != no.type && g[tx][ty] != 1 ){ q.push ( {tx,ty,no.cnt+1 ,no.type} ); } } } } int main (){ ios::sync_with_stdio (false ); cin.tie (0 ); cin >> n >> m; for (int i = 0 ;i < n ; i++){ for (int j = 0 ; j < m ; j++){ cin >> g[i][j]; } } cout << bfs () << endl; return 0 ; }
7.4____启发式搜索
启发式搜索(英文:heuristic search) 是一种改进的搜索算法。它在普通搜索算法的基础上引入了启发式函数,该函数的作用是基于已有的信息对搜索的每一个分支选择都做估价,进而选择分支。简单来说,启发式搜索就是对取和不取都做分析,从中选取更优解或删去无效解。
8 ____字符串
8.0 ____与字符串相关的一些算法
康拓展开(Cantor)
官方介绍:
康托展开是一个全排列到一个自然数的双射,常用于构建哈希表时的空间压缩。 康托展开的实质是计算当前排列在所有由小到大全排列中的顺序,因此是可逆的。
例子:
对于5个数的全排列
$1 1 1 1 1$ —映射→ 1
$11112$ —映射→ 2
康拓展开
计算这个排列为全排列的第几位
$X = a_n( n - 1 )! + a_{n-1}(n -2 )! +…+a_1*0! $
$a_i$的意思是从右向左的第 $i$ 位, 在他的右边有几个比他小的数。
注意:计算的时候 $12345$ 序列应视为第$0$个序列,后面会解释为什么。
拿$52413$举例子:
1、
首先看第一个数 $5$,不管第一位是什么数,后面都有四位数,那么这四位数全排列的方式有 4!种,而如果第一位是 $1$ 或 $2$ 或 $3$ 或 $4$ 都会比5开头的字典序要小,所以可以令$1,2,3,4$分别作为开头,这样的话就会有 $4 * 4!$种排法要比 $52413$ 这种排法的字典序要小。
那么第一个数是$1,2,3,4$时候的字典序的个数数完了是 $4 * 4!$ 种,且这些字典序都要比$52413$的字典序要小。
还有其他的排列方式比$52413$的字典序要小的吗?
2、
那么就可以固定第一位5,找下一位2,这时5已经用过了,所以从剩下的 1,2,3,4 里挑选比2小的数,一共1个,后面还剩三位,也就是3!种排列方式,那么这时候比 52413 字典序要小的又有 1 * 3!种,也就是当5在第一位,1在第二位的时候。
3、
再看第三位4,这时5,2都用了,所以从剩下的 1,3,4三个数中找比4小的数的个数,有两个比4小原理同上,所以这时候也可以有 $2 * 2!$ 种排列方式的字典序小于 52413
4、
再看第四位1,这时候会有 $0 * 1!$种
5、
再看第五位3,这时候会有$0 * 0!$种
综上所述:
对于序列: $52413$ 该序列展开后为: $4 * 4! + 1 * 3! + 2 * 2! + 0 * 1! + 0 * 0!$ ,计算结果是: 106 由于是从0开始计数的,所以最后 52413 的编号为 107
为什么从0开始计数? 可以这样看:我现在让你求12345的康托展开值,也就是:$04!+ 0 3!+ 02!+ 0 1!+0*0! = 0$
前10阶乘( 0 - 9)
1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880
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 <bits/stdc++.h> using namespace std;int facnum[20 ]; void fac (int n) { facnum[0 ] = facnum[1 ] = 1 ; for (int i = 2 ; i <= n ; i++) facnum[i] = facnum[i - 1 ] * i; return ; } int Cantor (string str) { int ans = 1 ; int len = str.length (); for (int i = 0 ; i < len ; i++) { int cnt = 0 ; for (int j = i + 1 ; j < len ; j++) if (str[i] > str[j]) cnt ++; ans += cnt * facnum[len - i - 1 ]; } return ans ; } int main () { fac (10 ); string str; str = "52413" ; cout << Cantor (str) << endl; return 0 ; }
康拓逆展开
由上:
如果初始序列是12345(第一个),让你求第107个序列是什么。(按字典序递增)
这样计算:
先把107减1
,因为康托展开里的初始序列编号为0 然后计算后缀积
:
1 2 3 4 5 5! 4! 3! 2!1! 0! 120 24 6 2 1 1
$106 / 4! = 4$ ····· 10 有4个比它小的所以因该是5 从(1,2,3,4,5)里选 $10 / 3! = 1$ ······ 4 有1个比它小的所以因该是2 从(1, 2, 3, 4)里选 $4 / 2! = 2$ ······ 0 有2个比它小的所以因该是4 从(1, 3, 4)里选 $0 / 1! = 0$ ······ 0 有0个比它小的所以因该是1 从(1,3)里选 $0 / 0! = 0$ ······ 0 有0个比它小的所以因该是3 从(3)里选
所以编号107的是 52413
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 #include <bits/stdc++.h> using namespace std;int facnum[20 ]; string str; int n; int num; vector<char > vec; void fac (int n) { facnum[0 ] = facnum[1 ] = 1 ; for (int i = 2 ; i <= n ; i++) facnum[i] = facnum[i - 1 ] * i; return ; } string deCantor (int k) { int len = vec.size (); string ans = "" ; k --; for (int i = 1 ; i <= len ; i++) { int t = k / facnum[ len - i]; k %= facnum[len - i]; ans += vec[t]; vec.erase (vec.begin () + t); } return ans; } int main () { fac (10 ); cin >> n; for (int i = 1 ; i <= 10 ; i++) if ( n / facnum[i] == 0 ) { num = i; break ; } for (int i = 1 ; i <= num ; i++) vec.push_back (i + '0' ); cout << deCantor (n) << endl; return 0 ; }
8.1 ____前缀函数与KMP (O(n + m))8.1.1____前缀函数
给定一个长度为 $n$的字符串$s$ ,其 前缀函数 被定义为一个长度为 $n$ 的数组 $\pi$ :
朴素计算前缀的方法 $O(N^3)$
1 2 3 4 5 6 7 8 9 void get_next () { for (int i = 1 ; i < n ; i++) for (int j = i ; j >= 0 ; j--) if ( s.substr (0 ,j) == s.substr (i-j+1 , j) ){ ne[i] = j; break ; } }
优化j开始的位置,一次变化最多 +1 $O(N^2)$
1 2 3 4 5 6 7 8 9 void get_next () { for (int i = 1 ; i < n ; i++) for (int j = ne[i - 1 ] + 1 ; j >= 0 ; j--) if ( s.substr (0 ,j) == s.substr (i-j+1 , j) ){ ne[i] = j; break ; } }
8.1.2____KMP算法
KMP暴力枚举做法
1 2 3 4 5 6 7 8 for (int i = 1 ; i <= n ; i++){ bool flag = 1 ; for (int j = 1 ; j <= m ; j++){ if ( s[i] != p[j] ){ flag = 0 ; break ; } }
算法模板(从1开始)
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 char p[maxn],s[maxm]; int ne[maxn]; int n,m;void get_next () { for (int i = 2 , j = 0 ; i <= n; i ++ ){ while (j && p[i] != p[j + 1 ]) j = ne[j]; if (p[i] == p[j + 1 ]) j ++ ; ne[i] = j; } } void KMP () { for (int i = 1 , j = 0 ; i <= m; i ++ ){ while (j && s[i] != p[j + 1 ]) j = ne[j]; if (s[i] == p[j + 1 ]) j ++ ; if (j == n){ j = ne[j]; } } } int main () { cin >> n >> p + 1 >> m >> s + 1 ; get_next (); KMP (); return 0 ; }
算法模板(从0开始)
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 void get_next (string p) { int len = p.length (); int i = 0 , j = -1 ; ne[0 ] = -1 ; while (i < len) { if (~j && p[i] != p[j]) j = ne[j]; else ne[++i] = ++j; } } bool kmp (string p) { get_next (p); int i =0 ,j=0 ,len = s.length (),le = p.length ();; while (i < len ) { if ( ~j && s[i] != p[j] ) j = ne[j]; else i++,j++; if (j >= le){ } } }
8.1.3____next[]数组在字符串周期性中的应用
对于字符串周期性的解释
对字符串 $s$ 和 $ 0< p \leq |s| $ , 若 $s[i]=s[i+p]$ 对所有$ i\epsilon [0,|s|-p-1]$ 成立,则称$p$是$s$的周期
8.1.4____最长公共子串(小串,多集合)
如 1 < s.length() < 200 && 1<n<4000(下面的例题,应该是没有跑满,跑满会超时)
Corporate Identity [链接](Corporate Identity - HDU 2328 - Virtual Judge (vjudge.net) ) 取第一个的串所有的子串,求子串的所有ne数组,然后匹配所有其他的串
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 #include <bits/stdc++.h> using namespace std;int n;int ne[210 ];char s[4005 ][300 ];void get_next (string p) { int len = p.length (),i = 0 , j = -1 ; memset (ne,0 ,sizeof ne); ne[0 ] = -1 ; while (i < len ){ if ( ~j && p[i] != p[j] ) j = ne[j]; else ne[++i] = ++j; } } bool check (string p) { get_next (p); for (int k = 1 ; k < n ; k++){ int le = p.length (),i = 0 , j = 0 ,len = strlen (s[k]); bool flag = 0 ; while (i < len){ if ( ~j && s[k][i] != p[j]) j = ne[j]; else i++ ,j ++; if ( j >= le){ flag = 1 ; break ; } } if (!flag) return false ; } return true ; } int main () { ios::sync_with_stdio (false ); cin.tie (0 ); while (cin >> n){ if ( n == 0 ) break ; memset (s,0 ,sizeof s); string p; cin >> p; for (int i = 1 ; i < n; i++){ cin >> s[i]; } int len = p.length (); string ans = "" ; for (int i = 0 ; i < len ; i++) for (int j = 1 ; j + i - 1 < len ; j++){ string te = p.substr (i,j); if ( check (te) ){ if (te.length () > ans.length ()) ans = te; else if ( te.length () == ans.length () && te < ans){ ans = te; } } } if (ans == "" ) cout << "IDENTITY LOST" <<endl; else cout <<ans << endl; } return 0 ; }
8.2____Z函数(扩展KMP)
Z函数的朴素算法$O(N^2)$
1 2 3 4 5 6 7 void get_z () { for (int i = 2 ; i <= n ; i++){ while (i + z[i] <= n && p[ z[i] + 1 ] == p[ i + z[i] ]) ++z[i]; } }
模板题
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 const int N = 2e7 +10 ;int q,ne[N],ex[N]; int slen,tlen; char s[N],t[N]; void get_next () { ne[0 ] = tlen; int now = 0 ; while (t[now] == t[1 + now] && now + 1 < tlen) now++; ne[1 ] = now; int p0 = 1 ; for (int i = 2 ; i < tlen ; i++){ if ( i + ne[i - p0] < ne[p0] + p0 ) ne[i] = ne[i - p0]; else { int now = ne[p0] + p0 - i; now = max (now, 0 ); while ( t[now] == t[i + now] && i + now < tlen ) now++; ne[i] = now; p0 = i ; } } } void exkmp () { get_next (); int now = 0 ; while ( s[now] == t[now] && now < min (slen ,tlen) ) now++; ex[0 ] = now; int p0= 0 ; for (int i = 1 ; i < slen ; i++){ if ( i + ne[i - p0] < ex[p0] + p0 ) ex[i] = ne[i - p0]; else { int now = ex[p0] + p0 - i; now = max (now, 0 ); while (t[now] == s[i + now] && now < tlen && now + i < slen) now++; ex[i] = now; p0= i; } } }
8.3 ____哈希表8.3.1 ____字符串前缀哈希法
概念
str = " ABCACB"
h[0] = 0
的哈希值
h[1] = " A"
的哈希值
h[2] = " AB"
的哈希值
h[3] = " ABC"
的哈希值
——> Hash[] 哈希数组就为str的前缀哈希值
把A,B,C,D看成 p 进制数
哈希映射表:
A B C D
1 2 3 4
→ “ABCD”
= ( 1 * p^3 + 2 * p^2 + 3 * p^1 + 4 * p ^0 ) mod Q
一般来说:
p = 131 或 13331
Q = 2^64
冲突的值最小
那么如何计算str中 [l,r]区间的哈希值呢
p^i 次方是以逆序方式递减的
求[l,r]的公式为:
hash = ( ( hash[r] − hash[l−1] ∗ p^r − l + 1 ) % MOD + MOD ) % MOD
算法思路
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 typedef unsigned long long ull;const int N = 100010 , M = 131 ; ull p[N],h[N]; int n,m;char str[N];ull get (int l , int r) { return h[r] - h[l - 1 ] * p[ r - l ]; } int main () { cin >> n >> m; cin >> str; p[0 ] = 131 ; h[0 ] = str[0 ]; for (int i = 1 ; i < n ; i++){ h[i] = h[i - 1 ] * M + str[i]; p[i] = p[i - 1 ] * M; } while (m --){ int l,r,x,y; cin >> l >> r >> x >> y; if ( get (l-1 ,r-1 ) == get (x-1 ,y-1 ) ){ cout << "Yes" <<endl; } else cout << "No" << endl; } return 0 ; }
8.4____AC自动机
8.5____Tire字典树
高效的存储和查找字符串的数据结构
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 int son[N][26 ], cnt[N], idx; void insert (char *str) { int p = 0 ; for (int i = 0 ; str[i]; i ++ ) { int u = str[i] - 'a' ; if (!son[p][u]) son[p][u] = ++ idx; p = son[p][u]; } cnt[p] ++ ; } int query (char *str) { int p = 0 ; for (int i = 0 ; str[i]; i ++ ) { int u = str[i] - 'a' ; if (!son[p][u]) return 0 ; p = son[p][u]; } return cnt[p]; }
8.6____Manacher(回文)(O(N))
分别计算奇偶
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 string s; int d1[10000010 ]; int d2[10000010 ]; int n;void get_d1 () { for (int i = 0 , l = 0 , r = -1 ; i < n ; i++){ int k = (i > r)?1 :min ( d1[l+r-i],r-i ); while ( 0 <= i-k && i + k < n && s[i-k] == s[i +k] ) k++; d1[i] = k--; if ( i + k > r ){ l = i-k; r = i+k; } } } void get_d2 () { for (int i = 0 , l = 0 , r = -1 ; i < n ; i++){ int k = (i > r)?0 :min ( d2[l+r-i + 1 ],r-i+1 ); while ( 0 <= i-k-1 && i + k < n && s[i-k-1 ] == s[i +k] ) k++; d2[i] = k--; if ( i + k > r ){ l = i-k-1 ; r = i+k; } } } cout << d1[i]*2 -1 << ' ' << d2[i]*2 <<endl;
预处理版本
139. 回文子串的最大长度 - AcWing题库
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 #include <bits/stdc++.h> using namespace std;int len [2000010 ];char str[2000010 ];string s; int n ,mx,id;void init () { memset (str,0 ,sizeof str); int k = 0 ; str[k++] = '$' ; for (int i = 0 ; i < n ; i++){ str[k++] = '#' ; str[k++] = s[i]; } str[k++]= '#' ; n = k; } int manacher () { memset (len,0 ,sizeof len); int sum = 0 ; mx = 0 ; for (int i = 1 ; i < n ; i++){ if (i < mx) len[i] = min ( mx -i ,len[2 *id - i] ); else len[i] = 1 ; while ( str[i - len[i]] == str[i + len[i]] ) len[i] ++; if ( len[i] + i > mx ){ mx = len[i] + i; id = i; sum = max ( sum ,len[i] ); } } return sum-1 ; } int main () { ios::sync_with_stdio (false ); cin.tie (0 ); int t = 1 ; while (true ){ cin >> s; n = s.length (); if ( s == "END" ) break ; init (); int ans = manacher (); cout << "Case " << t << ": " << ans <<endl; t++; } return 0 ; }
9____计算几何
9.1____数学基础
正弦公式
$\frac{a}{sin(a)} = \frac{b}{sin(b)} = \frac{c}{sin(c)} = 2R$
其中,$R$ 为 $\Delta ABC$ 的外接圆
余弦公式
$a^2 = b^2 + c^2 - 2bc* cos(A)$ $b^2 = a^2 + c ^2 - 2accos(B)$ $c^2 = a^2 + b^2 - 2ab cos(C)$
海伦公式
$ p = \frac{a+b+c}{2} $
$S = \sqrt{p(p-a)(p-b)(p-c)}$
9.2____板子 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 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 const double eps = 1e-8 ;const double pi = acos ( -1.0 );int sgn (double x) { if ( fabs (x) < eps ) return 0 ; if ( x < 0 ) return -1 ; else return 1 ; } inline double sqr (double x) { return x * x; }struct Point { double x,y; Point (){} Point (double _x,double _y) { x = _x , y = _y; } bool operator == (Point b) const { return sgn (x - b.x) == 0 && sgn (y - b.y) == 0 ; } bool operator < (Point b) const { return sgn (x - b.x) == 0 ? sgn (y - b.y) < 0 : x < b.x; } Point operator - (const Point &b) const { return Point (x - b.x , y - b.y); } Point operator + (const Point &b) const { return Point (x + b.x , y + b.y); } Point operator * (const double &k) const { return Point (x * k , y * k ); } Point operator / (const double &k) const { return Point (x / k , y / k); } double operator ^ (const Point &b) const { return x * b.y - y * b.x; } double operator * (const Point &b) const { return x * b.x + y * b.y; } double len () { return hypot (x,y); } double len2 () { return x * x + y * y; } double distance (Point p) { return hypot ( x - p.x , y - p.y ); } double rad (Point a,Point b) { Point p = *this ; return fabs ( atan2 ( fabs ( (a-p)^(b-p) ) , (a-p)*(b-p) ) ); } Point trunc (double r) { double l = len (); if ( !sgn (l) ) return *this ; r /= l; return Point (-y,x); } Point rotleft () { return Point (y,-x); } Point rotright () { return Point (y,-x); } Point rotata (Point p,double angle) { Point v = (*this ) - p; double c = cos (angle) , s = sin (angle); return Point (p.x + v.x * c - v.y * s , p.y + v.x *s + v.y * c); } }; struct Line { Point s,e; Line (){} Line ( Point _s, Point _e ){ s =_s ; e=_e; } void input ( Point _s, Point _e ) { s =_s ; e=_e; } Line (Point p,double angle){ s = p; if ( sgn (angle - pi/2 ) == 0 ) e = (s + Point (0 ,1 )); else e = (s + Point (1 ,tan (angle))); } Line (double a,double b,double c){ if ( sgn (a) == 0 ) { s = Point (0 ,-c/b); e = Point (1 ,-c/b); } else if (sgn (b) == 0 ) { s = Point (-c/a,0 ); e = Point (-c/a,1 ); } else { s = Point (0 ,-c/b); e = Point (1 ,(-c-a)/b); } } double length () { return s.distance (e);} bool linecrossseg (Line v) { return sgn ( (v.s - e) ^ (s - e) ) * sgn (( v.e-e ) ^ (s -e) ) <= 0 ; } int relation (Point p) { int c = sgn ( (p-s) ^ (e -s) ); if (c < 0 ) return 1 ; else if (c > 0 ) return 2 ; else return 3 ; } bool point_on_seg (Point p) { return sgn ((p-s)^(e-s) ) == 0 && sgn ( (p-s)*(p-e) ) <= 0 ; } bool parallel (Line v) { return sgn ( (e-s)^( v.e - v.s ) ) == 0 ; } int linecrossline (Line v) { if ( (*this ).parallel (v) ) return v.relation (s) == 3 ; return 2 ; } Point crosspoint (Line v) { double a1 = ( v.e - v.s ) ^ ( s - v.s ); double a2 = ( v.e - v.s ) ^ ( e - v.s ); return Point ( (s.x * a2 - e.x * a1)/(a2 - a1) , (s.y *a2 - e.y *a1)/(a2 - a1)); } double dispointtoseg (Point p) { if ( sgn ( (p - s)*(e - s) < 0 ) || sgn ( (p-e)*(s-e) ) < 0 ) return min ( p.distance (s),p.distance (e) ); return dispointtoline (p); } double dispointtoline (Point p) { return fabs ( (p-s)^(e-s) ) / length (); } Point lineprog (Point p) { return s + ( ( (e-s)*((e-s)*(p-s)) ) / ( (e-s).len2 () ) ); } int segcrossseg (Line v) { int d1 = sgn ((e - s) ^ (v.s - s)); int d2 = sgn ((e - s) ^ (v.e - s)); int d3 = sgn ((v.e - v.s) ^ (s - v.s)); int d4 = sgn ((v.e - v.s) ^ (e - v.s)); if ((d1 ^ d2) == -2 && (d3 ^ d4) == -2 )return 2 ; return (d1 == 0 && sgn ((v.s - s) * (v.s - e)) <= 0 ) || (d2 == 0 && sgn ((v.e - s) * (v.e - e)) <= 0 ) || (d3 == 0 && sgn ((s - v.s) * (s - v.e)) <= 0 ) || (d4 == 0 && sgn ((e - v.s) * (e - v.e)) <= 0 ); } }; struct triangle { Point A,B,C; Line a,b,c; triangle (){} triangle (Point _A,Point _B,Point _C){ A = _A ; B = _B ; C = _C;} Point incenter () { return Point ( ( A.x + B.x + C.x ) / 3 , ( A.y + B.y + C.y ) / 3 ); } }; void cal (int a,int b,int c) { double a1 = p[b].x - p[a].x, b1 = p[b].y - p[a].y, c1 = (a1*a1 + b1*b1)/2 ; double a2 = p[c].x - p[a].x, b2 = p[c].y - p[a].y, c2 = (a2*a2 + b2*b2)/2 ; double d = a1 * b2 - a2 * b1; x = p[a].x + (c1*b2 - c2*b1)/d,y = p[a].y + (a1*c2 - a2*c1)/d; r = dis2 (a); } struct circle { Point p; double r; circle (){} circle ( Point _p,double _r ) { p = _p ; r = _r; } bool operator == (circle v){ return (p == v.p) && sgn (r - v.r) == 0 ; } bool operator < (circle v) const { return ( (p<v.p) || (p == v.p) && sgn ( r - v.r ) < 0 ); } double area () { return pi*r*r; } double circumference () { return 2 *pi*r; } int relation (Point b) { double dst = b.distance (p); if ( sgn (dst - r) < 0 ) return 2 ; else if ( sgn (dst-r) == 0 ) return 1 ; return 0 ; } int relationseg (Line v) { double dst = v.dispointtoseg (p); if ( sgn (dst - r) < 0 ) return 2 ; else if ( sgn (dst - r) == 0 ) return 1 ; return 0 ; } int relationline (Line v) { double dst = v.dispointtoline (p); if ( sgn (dst - r) == 0 ) return 2 ; else if ( sgn ( dst - r) == 0 ) return 1 ; return 0 ; } int pointcrossline (Line v,Point &p1,Point &p2) { if ( !(*this ).relationline (v) ) return 0 ; Point a = v.lineprog (p); double d = v.dispointtoline (p); d = sqrt (r*r - d*d); if ( sgn (d) == 0 ){ p1 = a,p2 = a; return 1 ; } p1 = a + (v.e - v.s).trunc (d); p2 = a - (v.e - v.s).trunc (d); return 2 ; } double areatriangle ( Point a,Point b ) { if ( sgn ((p-a)^(p-b)) == 0 ) return 0.0 ; Point q[5 ]; int len =0 ; q[len++] = a; Line l (a,b) ; Point p1,p2; if ( pointcrossline ( l,q[1 ],q[2 ] ) == 2 ){ if ( sgn ( ( a - q[1 ] )*( b - q[1 ] ) ) < 0 ) q[len ++] = q[1 ]; if ( sgn ( ( a - q[2 ] )*( b - q[2 ] ) ) < 0 ) q[len ++] = q[2 ]; } q[len ++] = b; if ( len == 4 && sgn ( (q[0 ]-q[1 ])*(q[2 ]-q[1 ]) ) > 0 ) swap ( q[1 ],q[2 ] ); double res = 0 ; for (int i = 0 ; i < len - 1 ; i++){ if ( relation (q[i]) == 0 || relation ( q[i + 1 ] ) == 0 ){ double arg = p.rad ( q[i],q[i + 1 ] ); res += r*r*arg/2.0 ; } else { res += fabs ( (q[i] - p) ^ ( q[i+ 1 ] - p ) ) / 2.0 ; } } return res; } }; const int maxp = 1100 ;const int maxl = 2200 ;struct polygon { int n; Point p[maxp]; Line l[maxl]; struct cmp { Point p; cmp (const Point &p0){ p = p0;} bool operator () ( const Point &aa ,const Point &bb) { Point a = aa,b = bb; int d = sgn ( (a-p)^(b-p) ); if (d == 0 ) return sgn ( a.distance (p) - b.distance (p)) < 0 ; return d > 0 ; } }; void norm () { Point mi = p[0 ]; for (int i = 1 ; i < n; i ++) mi = min (mi,p[i]); sort (p, p + n, cmp (mi) ); } int relationpoint (Point tep) { for (int i = 0 ; i < n ; i++){ if ( p[i] == tep ) return 3 ; } for (int i = 0 ; i < n; i++){ if ( l[i].point_on_seg (tep) ) return 2 ; } int tecnt = 0 ; for (int i = 0 ; i < n ; i++) { int j = (i + 1 ) % n; int c = sgn ( (tep - p[j]) ^ (p[i] - p[j]) ); int u = sgn ( p[i].y - tep.y ); int v = sgn ( p[j].y - tep.y ); if ( c > 0 && u < 0 && v >=0 ) tecnt ++; if ( c < 0 && u >= 0 && v < 0 ) tecnt --; } return tecnt != 0 ; } void getconvex (polygon &convex) { sort (p , p + n); convex.n = n; for (int i = 0 ; i < min (n,2 ) ; i++){ convex.p[i] = p[i]; } if ( convex.n == 2 && (convex.p[0 ] == convex.p[1 ]) ) convex.n--; if ( n <= 2 ) return ; int &top = convex.n; top = 1 ; for (int i = 2 ; i < n ; i++){ while (top && sgn ( (convex.p[top] - p[i]) ^ (convex.p[top-1 ] - p[i])) <= 0 ) top --; convex.p[++top] = p[i]; } int temp = top; convex.p[++top] = p[n-2 ]; for (int i = n - 3 ; i >=0 ; i--) { while ( top!=temp && sgn ( (convex.p[top] - p[i]) ^ (convex.p[top-1 ] - p[i]) ) <=0 ) top--; convex.p[++top] = p[i]; } if ( convex.n == 2 && ( convex.p[0 ] == convex.p[1 ]) ) convex.n --; convex.norm (); } bool isconvex () { bool s[2 ]; memset (s,false ,sizeof (s)); for (int i = 0 ; i < n ; i++){ int j = (i + 1 ) % n; int k = (j + 1 ) % n; s[ sgn ((p[j] - p[i]) ^ (p[k]-p[i]) ) + 1 ] =true ; if ( s[0 ] && s[2 ]) return false ; } return true ; } double getcircumference () { double sum = 0 ; for (int i = 0 ; i < n ; i++){ sum += p[i].distance ( p[(i + 1 )%n] ); } return sum; } double getarea () { double sum = 0 ; for (int i = 0 ; i < n ; i++){ sum += ( p[i]^p[ (i+1 )%n ] ); } return fabs (sum)/2 ; } Point getbarycentre () { Point ret (0 ,0 ) ; double area = 0 ; for (int i = 1 ; i < n - 1 ; i ++){ double tmp = ( p[i] - p[0 ] ) ^ (p[i + 1 ] - p[0 ]); if ( sgn (tmp) == 0 ) continue ; area += tmp; ret.x += ( p[0 ].x + p[i].x + p[i + 1 ].x ) / 3 * tmp; ret.y += ( p[0 ].y + p[i].y + p[i + 1 ].y ) / 3 * tmp; } if ( sgn (area) ) ret = ret / area; return ret; } double areacircle (circle c) { double ans = 0 ; for (int i = 0 ; i < n ; i++) { int j = (i + 1 ) %n; if ( sgn ( (p[j] - c.p) ^ ( p[i] - c.p )) >= 0 ) ans += c.areatriangle ( p[i],p[j] ); else ans -= c.areatriangle ( p[i],p[j] ); } return fabs (ans); } int relationcircle (circle c) { int x = 2 ; if ( relationpoint (c.p) != 1 ) return 0 ; for (int i = 0 ; i < n ; i++){ if ( c.relationseg (l[i] ) == 2 ) return 0 ; if ( c.relationseg (l[i] ) == 1 ) x = 1 ; } return x; } };
快乘与快速幂 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 inline ll mult_mod (ll a, ll b, ll m) { ll res = 0 ; while (b){ if (b&1 ) res = (res+a)%m; a = (a+a)%m; b >>= 1 ; } return res; } ll pom (ll a, ll b ,ll mod) { ll ans = 1 ; while (b){ if (b&1 ) ans = (ans * a) % mod; a = (a * a) % mod; b=b>>1 ; } return ans; }
10____JAVA在ACM中的应用 10.1____Jave在竞赛中的基本操作
1.输入输出
输入 Scaner cin = new Scanner( System.in );
while( cin.hasNext() )
相当于!= EOF
int n = cin.nextInt();
读入一个int类型的数
BigInteger bi = cin.nextBigInteger();
System.out.print(n);
输出n但不换行
System.out.println();
换行
System.out.println(n);
输出n并换行
System.out.printf(“%d\n”,n);
类似C语言中的输出
2.定义变量
BigInteger任意大的数,原则上只要你的计算机内存足够大,可以有无限位
10.2____BigInteger大整数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import java.math.BigInteger;import java.util.Scanner;BigInteger a,b,c; a = new BigInteger("0" ); b = new BigInteger("11" ); c = new BigInteger("100" ); a = b.add(c); b = a.subtract(c); c = b.multiply(a); a = c.divide(b); a = c.remainder(b) == c.mod(b) c = a.gcd(b); c = a.max(b); c = a.min(b); if ( a.equal(b) ){...}
10.3____BigDecimal大浮点数
10.3.1____浮点大数的格式化输出 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 DecimalFormat df = new DecimalFormat("0" ); BigDecimal big; Scanner cin = new Scanner( System.in ); big = cin.nextBigDecimal(); big = big.setScale(0 , BigDecimal.ROUND_DOWN); System.out.println( df.format(big) );
10.3.2____浮点大数的setscale方法() 1 2 3 4 5 6 7 8 9 10 11 12 13 BigDecimal big = new BigDecimal("2.624124" ); BigDecimal a = new BigDecimal("0" );
快读与快输 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 inline int read () { int x=0 ,f=1 ; char ch=getchar (); while (ch<'0' ||ch>'9' ){ if (ch=='-' ) f=-1 ; ch=getchar (); } while (ch>='0' &&ch<='9' ){ x=(x<<1 )+(x<<3 )+(ch^48 ); ch=getchar (); } return x*f; } inline int write (int X) { if (X<0 ) {putchar ('-' ); X=~(X-1 );} int s[20 ],top=0 ; while (X) {s[++top]=X%10 ; X/=10 ;} if (!top) s[++top]=0 ; while (top) putchar (s[top--]+'0' ); }
线性筛质数,欧拉函数,莫比乌斯函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 int oula[maxn],mu[maxn],pri[maxn];int pr[maxn], top = 0 ;void init () { for (int i = 2 ; i <= maxn; ++i){ if (!pri[i]){ pr[top++] = i; oula[i] = i - 1 ; mu[i] = -1 ; } for (int j = 0 ; j < top && i * pr[j] <= maxn; ++j){ pri[i * pr[j]] = 1 ; if (i % pr[j]){ oula[i * pr[j]] = oula[i] * (pr[j] - 1 ); mu[i * pr[j]] = -mu[i]; } else { oula[i * pr[j]] = oula[i] * pr[j]; mu[i * pr[j]] = 0 ; break ; } } } }
数被整除的条件
能被2整除的数,个位 上%2等于0
能被3整除的数,各位相加 %3等于0
能被4整除的数,个位加十位 %4等于0
能被5整除的数,个位 %5等于0
能被6整除的数,各位相加 %3等于0 且/3后%2等于0
能被7整除的数,若一个整数的个位数字截去,余下的数减去当前个位数*2%7等于0
能被8整除的数,**%1000**后%8等于0
能被9整除的数,各位相加 %9等于0
DFS与BFS
BFS
带权并查集与种类并查集 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 int get (int x) { if (x == dp[x]) return x; int y = father[x]; father[x] = get (father[x]); wei[x] += wei[y]; } void mer (int x, int y ) { int a = get (x); int b = get (y); if (a!=b){ father[a] = b; } } n = 1000 ; int a[1000 * m + 5 ]
单调队列解决固定区间最大最小问题 1 2 3 4 5 6 7 8 9 int l = 0 , r = 1 ;dp[0 ] = 1 ; if (m==1 ) printf ("%d\n" ,a[1 ]); for (int i = 2 ; i <= n ; i++){ if (i-du[l] >=m && l<r) l++; while (r>l&&a[du[r-1 ]] >= a[i]) r--; du[r++] = i; if (i >= m) printf ("%d" ,a[du[l]]); }
RMQ问题与st数组、线段树 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 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 int m,s[10000 ],n,dp[10000 ][500 ],en; void rmq_st (int n ) { memset (dp,0x3f ,sizeof (dp)); for (int i = 1 ; i <= n ; i++) dp[i][0 ] = s[i]; m = (int ) (log (1.0 * n) / log (2.0 )); for (int j = 1 ; j <= m ; j++){ int t = n - (1 <<j) + 1 ; for (int i = 1 ; i <= t; i++){ dp[i][j] = min (dp[i][j-1 ],dp[i+(1 <<(j-1 ))][j-1 ]); cout<<dp[i][j]<<" " ; } printf ("\n" ); } } void solve () { cin>>n>>en; for (int i = 1 ; i <= n ;i++) cin>>s[i]; rmq_st (n); for (int i = 1 ; i <= en; i++){ int l ,r; cin>>l>>r; int k = (int )(log (1.0 *(r-l+1 ))/log (2.0 )); cout<<min (dp[l][k],dp[r-(1 <<k)+1 ][k])<<endl; } } int dp[Max*3 ],lazy[Max*3 ];int mn,n,m,k,flag;void pushdown (int p,int l,int r) { if (!lazy[p]) return ; lazy[p*2 +1 ]+= lazy[p]; lazy[p*2 ]+=lazy[p]; int mid=(l+r)/2 ; dp[p*2 ]+=(mid-l+1 )*lazy[p]; dp[p*2 +1 ]+=(r-mid)*lazy[p]; lazy[p]=0 ; } void modi (int p, int l, int r,int x,int y,int va) { if (x>r||y<l) return ; if (x<=l&&y>=r){ lazy[p]+=va; dp[p]+= va*(r-l+1 ); return ; } pushdown (p,l,r); int mid=(l+r)/2 ; modi (p*2 ,l,mid,x,y,va); modi (p*2 +1 ,mid+1 ,r,x,y,va); dp[p]=dp[p*2 ]+dp[p*2 +1 ]; } void creat (int l,int r,int p) { if (l == r){ dp[p]= a[l]; return ; } int mid=(l+r)/2 ; creat (l,mid,p*2 ); creat (mid+1 ,r,p*2 +1 ); dp[p]=dp[p*2 ]+dp[p*2 +1 ]; lazy[p]=0 ; } ll query (int p, int l, int r, int x, int y) { if (x>r||y<l) return 0 ; if (x<=l&&y>=r){ return dp[p]; } pushdown (p,l,r); int mid=(l+r)/2 ; ll sum=0 ; sum+=query (p*2 ,l,mid,x,y); sum+=query (p*2 +1 ,mid+1 ,r,x,y); dp[p]=dp[p*2 ]+dp[p*2 +1 ]; return sum; }
**spfa算法**
可以处理带负权的边
可以得到负圈是否存在,不能输出负圈
用一个数组存储每个点入队次数 超过n次即存在负圈
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 typedef struct { int v,cost; }pe; int dp[1005 ],n,m,cn[1005 ]; bool vi[1005 ];int flag; void spfa () { queue<int > pri; scanf ("%d%d" ,&n,&m); fill (dp,dp+n+2 ,2147483 ); vector<pe> a[n+2 ]; for (int i=0 ;i<m;i++){ int x,y,z; scanf ("%d%d%d" ,&x,&y,&z); pe te; te.v=y; te.cost=z; a[x].push_back (te); te.v=x; a[y].push_back (te); } memset (vi,0 ,sizeof (vi)); pri.push (1 ); vi[1 ] = 1 ; dp[1 ] = 0 ; cn[1 ]++; while (!pri.empty ()){ int te = pri.front (); pri.pop (); vi[te] = 0 ; cn[te]++; if (cn[te] > n){ flag = 1 ; break ; } for (int i = 0 ; i < a[te].size (); i++){ int to = a[te][i].v; if (dp[to] > dp[te] + a[te][i].cost){ dp[to] = dp[te] + a[te][i].cost; if (!vi[to]){ vi[to] = 1 ; pri.push (to); } } } } if (flag) cout<<"-1" <<endl; cout<<dp[n]<<endl; }
链式前向星( 迪杰斯特与佛洛依德算法里 有其他俩种存储方式) 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 typedef struct { int to , next , cost; }sp; int head[1004 ]; sp cnt[1004 ]; void add (int u,int v,int w) { cnt[top].to = v; cnt[top].cost = w; cnt[top].next = head[u]; head[u] = top++; } void solve () { cin>>n; memset (head,-1 ,sizeof (head)); int top = 0 ; for (int i = 0 ; i < n; i++){ int x , y , w; cin>>x>>y>>w; add (x,y,w); } for (int i = 0 ; i < n;i++){ for (int j = head[i]; j != -1 ; j = cnt[j].next){ cout<<i<<" " <<cnt[j].to<<" " <<cnt[j].cost<<endl; } } }
**分块思想 (将一个序列分成几个小块)**
用树状数组 线段树 的题用这个也可 处理 区间加 乘 求和 查询 单点查询
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 int n,a[100 ],pos[100 ],add[100 ],d; 位置在第几个块 void add_1 (int l,int r,int c) { for (int i = l; i <= min (r,pos[l]*d); ++i) a[i]+=c; if (pos[l] != pos[r]){ for (int i = (pos[r]-1 )*d+1 ; i <= r; ++i) a[i]+=c; } for (int i = pos[l]+1 ; i <= pos[r]-1 ; ++i) add[i]+=c; } int query (int l,int r) { int ans=0 ; for (int i=l;i <= min (r,pos[l]*d); ++i) ans+=v[i]+add[pos[l]]; if (pos[l]!=pos[r]) for (int i=(pos[r]-1 )*d+1 ; i<=r ;++i) ans+=v[i]+add[pos[r]]; for (int i=pos[l]+1 ; i<=pos[r]-1 ;++i) ans+=sum[i]+d*add[i]; return ans; } int main () { int opt,l,r,c; cin>>n; d=sqrt (n); for (int i=1 ;i<=n;++i) cin>>a[i]; for (int i=1 ;i<=n;i++) pos[i]=(i-1 )/d+1 ; for (int i=1 ;i<=n;++i){ cin>>opt>>l>>r>>c; if (opt) cout<<a[r]+add[pos[r]]<<endl; else add_1 (l,r,c); } return 0 ; }
欧拉回路
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 无向图判断 const int MAX_N = 100 ;int mat[MAX_N][MAX_N];int match[MAX_N]; int n; void solve (int u) { if (match[u] > 0 ) { for (int i = 0 ; i < n; ++i) { if (mat[u][i]) { mat[u][i]--; mat[i][u]--; solve (i); } } } cout << "visiting " << u << endl; } 有向图判断 const int MAX_N = 100 ;const int MAX_M = 10000 ;int mat[MAX_N][MAX_N];int match[MAX_N]; int n; int stk[MAX_M], top = 0 ; void solve (int u) { if (match[u] > 0 ) { for (int i = 0 ; i < n; ++i) { if (mat[u][i]) { mat[u][i]--; solve (i); } } } stk[top++] = u; }
乘法逆元 求解(a/b)mod p 问题
通过求b关于p的逆元k 可以得到(a/b)mod p == (a * k) mod p
简单证明 : b * k %p = 1 等价于 bk = px + 1 即 k = (px+1)/b
再带k入 ak % p 中 (apx+a)/b %p 即(a/b %p + apx/b %p)%p a/b % p + (ax/b) p%p
即 a/b % p
优雅暴力——莫队 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 int qn = sqrt (n);int belong[MAXN+2 ];typedef struct { int l, r, di; ]sp; sp ans[MAXM]; bool cmp (sp a, sp b) { return belong[a.l] == belong[b.l] ? belong[a.r] < belong[b.r] : belong[a.l]<belong[b.l]; } for (int i = 1 ; i <= n; i++){ for (int j = (i-1 ) * qn + 1 ; j <= i * qn ; ++i){ belong[j] = i; } } sort (ans+1 , ans + 1 + m; cmp); for (int i=1 ;i<=m;i++) { int q1 = ans[i].l , q2 = ans[i].r; while (l < q1) del (l++); while (l > q1) add (--l); while (r < q2) add (++r); while (r > q2) del (r--); ans[Q[i].id]=Ans; }
字典树(Trie树) 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 const int maxn=10005 ;int dp[maxn][26 ];int head[26 ],top; char s[105 ];bool query () { if (head[s[0 ]-'a' ]==-1 ) return false ; int temp = head[s[0 ] - 'a' ]; for (int i = 1 ; i < strlen (s); i++){ if (dp[temp][s[i]-'a' ] == -1 ){ return false ; } temp = dp[temp][s[i]-'a' ]; } return true ; } void add () { if (head[s[0 ]-'a' ]==-1 ) head[s[0 ]-'a' ] = top++; int temp = head[s[0 ]-'a' ]; for (int i = 1 ; i < strlen (s); i++){ if (dp[temp][s[i]-'a' ] == -1 ){ dp[temp][s[i] - 'a' ] = top++; } temp = dp[temp][s[i] - 'a' ]; } } void slove () { memset (dp,-1 ,sizeof (dp)); memset (head,-1 ,sizeof (head)); top = 0 ; while (scanf ("%s" ,s)!=EOF){ int n; cin>>n; if (n==1 ) add (); else printf ("%d\n" ,query ()); } }
KMP(字符串匹配)
解决 判断a是否为b的子串(连续) 时间复杂度为O(N)
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 <cstdio> #include <iostream> #include <cstring> using namespace std;const int maxn=1e6 +5 ;char a[maxn],b[maxn];int Next[maxn];int n1,n2;void getnext () { int j=0 ,k=-1 ; while (j<n2){ if (k==-1 ||b[j]==b[k]){ if (b[++j]==b[++k]) Next[j]=Next[k]; else Next[j]=k; } else k=Next[k]; } } int kmp () { Next[0 ]=-1 ; getnext (); int i=0 ,j=0 ; while (i<n1&&j<n2){ if (j==-1 ||a[i]==b[j]){ i++; j++; }else { j=Next[j]; } } if (j==n2) return i-j; else return -1 ; } int main () { int t; cin>>t; while (t--){ scanf ("%s%s" ,a,b); n1=strlen (a); n2=strlen (b); cout<<kmp ()<<endl; } return 0 ; }
二分图
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 #incldue<iostream> #include <vector> #include <queue> #include <cstdio> using namespace std;const int maxn=1e3 +5 ;int color[maxn];vector <int > G[maxn]; bool dfs (int v, int c) { color[v] = c; for (int i = 0 ; i < n; i++){ if (G[v][i] == 1 ){ if (color[i] == c) return false ; if (color[i] == 0 && !dfs (i,-c)) return false ; } } return true ; } bool bfs (int u) { queue<int > q; q.push (u); col[u]=1 ; while (!q.empty ()) { int v=q.front (); q.pop (); for (int i=0 ;i<G[v].size ();i++) { int x=G[v][i]; if (col[x]==0 ) { col[x]=-col[v]; q.push (x); } else { if (col[x]==col[v]) return false ; } } } return true ; } int main () { solve (); return 0 ; }
最小生成树 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 typedef struct { int u,v,cost; }p; int dp[1005 ];int gkd (int x) { if (dp[x]==x) return x; return dp[x]=gkd (dp[x]); } bool cmp (p a,p b) { return a.cost<b.cost; } sort (en,en+top,cmp); for (int i=0 ;i<n;i++) dp[i]=i; for (int i=0 ;i<top;i++){ int c=gkd (en[i].u); int d=gkd (en[i].v); if (c!=d){ dp[c]=d; ans+=en[i].cost; } } printf ("%d\n" ,ans);
tarjin算法 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 int dfn[maxn],low[maxn],top = 0 ,k = 0 ;戳最早的点的dfn top即时间戳 k表示有几个强连通分量 int cn[maxn],cnt[maxn];set<int > inqu; stack<int > qu; void tarjin (int x) { dfn[x] = low[x] = top++; qu.push (x); inqu.insert (x); for (int i = 0 ; i < ve[x].size (); i++){ int te = ve[x][i]; if (!vi[te]){ tarjin (te); low[x] = min (low[x],low[te]); 时间戳则更新这个点的low[x] } else if (inqu.count (x)){ low[x] = min (low[x],dfn[te]); } } if (dfn[x] == low[x]){ k++; do { int te = qu.top (); qu.pop (); inqu.erase (); cn[te] = k ,cnt[k]++; }while (te != x); } } memset (dfn,0 ,sizeof (dfn));for (int i = 1 ; i <= n; i++) if (!dfn[x]) tarjin (x);
数位dp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 int dp[20 ][2 ],a[20 ];int dfs (int pos,int sta,bool limit) { if (pos==0 ) return 1 ; if (dp[pos][sta]==-1 ||limit){ int sum=0 ; int up=limit?a[pos]:9 ; for (int i=0 ;i<=up;i++){ if (sta==1 &&i==2 ) continue ; if (i==4 ) continue ; sum+=dfs (pos-1 ,i==6 ,limit&&i==a[pos]); } if (limit) return sum; dp[pos][sta]=sum; } return dp[pos][sta]; }
状压dp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 for (int i = 0 ; i<(1 <<n);i++){ for (int j = i; j ;j=(j-1 )&i){ if (wn[j]<=w){ dp[i]=min (dp[i],dp[i-j]+sn[j]); } } } vector <int > flag; vector <int > fl[maxn + 2 ]; int er[maxn]; void init () { for (int i = 0 ; i < 1 << n; ++i){ if (!(i & (i >> 1 )) && !( i & (i >> 2 ))) flag.push_back (i); } for (int i = 0 ; i < n; ++i) cin>>er[i]; for (int i = 0 ; i < n; ++i){ for (int j = 0 ; j < flag.size (); ++j){ if ((flag[j] & er[i]) == er[i]) fl[i].push_back (flag[j]); } } }
优先队列 #include 和队列基本操作相同: top 访问队头元素 empty 队列是否为空 size 返回队列内元素个数 push 插入元素到队尾 (并排序) emplace 原地构造一个元素并插入队列 pop 弹出队头元素 swap 交换内容
priority_queue <int,vector,greater > q; //降序队列,大顶堆 priority_queue <int,vector,less >q; //greater和less是std实现的两个仿函数(就是使一个类的使用看上去像一个函数。其实现就是类中实现一个operator(),这个类就有了类似函数的行为,就是一个仿函数类了) ①基本类型优先队列的例子:
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 ②用pair做优先队列元素的例子://规则:pair的比较,先比较第一个元素,第一个相等比较第二个。 //方法1 struct tmp1 //运算符重载< { int x; tmp1(int a) {x = a;} bool operator<(const tmp1& a) const { return x < a.x; //大顶堆 } }; //方法二 struct tmp2 //重写仿函数 { bool operator() (tmp1 a, tmp1 b) { return a.x < b.x; //大顶堆 } }; int main() { tmp1 a(1); tmp1 b(2); tmp1 c(3); priority_queue<tmp1> d; d.push(b); d.push(c); d.push(a); while (!d.empty()) { cout << d.top().x << '\n'; d.pop(); } cout << endl; priority_queue<tmp1, vector<tmp1>, tmp2> f; f.push(b); f.push(c); f.push(a); while (!f.empty()) { cout << f.top().x << '\\n'; f.pop(); }
string 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 //获取字符串长度 **int length = str1.length();** //字符串连接 **string str4 = str1 + str3;** //字符串比较 **if (str1 < str3)** //获取字符串的第一个字符 **string::const_iterator it = str1.begin();** //获取字符串的最后一个字符 **it = str1.end();** //倒置串 **reverse(str1.begin(), str1.end());** //查找串 //find-从指定位置起向后查找,直到串尾 string st1("babbabab"); **cout << st1.find('a') << endl;**//默认从位置0(即第1个字符)开始查找 **cout << st1.find('a', 2) << endl;**//在st1中,从位置2(b,包括位置2)开始查找a返回匹配的位置 **cout << (st1.find('c', 0) == -1)** << endl;//1 **cout << st2.find(str1, 2) << endl;**//从st2的位置2开始匹配,返回第一次成功匹配时匹配的串 str1的首字符在st2中的位置,失败返回-1 //rfind-从指定位置起向前查找,直到串首 **cout << st1.rfind('a', 7) << endl;**
博弈论
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 巴什博伊:只有一堆n个物品,两个人轮流从这堆物品中取物,规 定每次至少取一个,最多取m个。 最后取光者得胜。 分析:如果n=m+1 ,无论先取者拿走多少,后取者必胜。 给对手留下m+1 的倍数 那么就是必胜局 int main (){ long long n,m,a; while (cin>>n>>m) { if (n==m+1 ) cout<<后者胜利; else { if ((n%(m+1 ))<=m&&(n%(m+1 ))!=0 ) cout<<前者胜利; else cout<<后者胜利 ; } } return 0 ; } 威佐夫博弈:有两堆各若干个物品,两个人轮流从某一堆或同时从两堆中取同样多的物品, 规定每次至少取一个,多者不限,最后取光者得胜。 奇异局势公式:a[k]=[k*(1 +√5 )/2 ],b[k]=a[k]+k 当面对奇异局势时 必输 以有的奇异局势:(0 ,0 )、(1 ,2 )、(3 ,5 )、(4 ,7 )、(6 ,10 ) (8 ,13 )、(9 ,15 )、(11 ,18 )、(12 ,20 ) int main () { int A, B; while (cin >> A >> B) { if (A > B) swap (A, B); int K = B - A; if ((int )(K * (1 + sqrt (5 )) / 2 ) == A) cout << 后者胜利 << endl; else cout << 前者胜利 << endl; } } 一堆中取若干根,可将一堆全取走,但不可不取,最后取完者为胜 int main () { int a=0 ; cin>>n; for (int i=0 ;i<n;++i){ cin>>m; a^=m; } if (a) 先手赢 else 后手赢 }
高精度 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 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 正整数间的加 乘 除 余 减 (减法结果不能为负数) struct bign { int d[maxn], len; void clean () { while (len > 1 && !d[len-1 ]) len--; } bign () { memset (d, 0 , sizeof (d)); len = 1 ; } bign (int num) { *this = num; } bign (char * num) { *this = num; } bign operator = (const char * num){ memset (d, 0 , sizeof (d)); len = strlen (num); for (int i = 0 ; i < len; i++) d[i] = num[len-1 -i] - '0' ; clean (); return *this ; } bign operator = (int num){ char s[20 ]; sprintf (s, "%d" , num); *this = s; return *this ; } bign operator + (const bign& b){ bign c = *this ; int i; for (i = 0 ; i < b.len; i++){ c.d[i] += b.d[i]; if (c.d[i] > 9 ) c.d[i]%=10 , c.d[i+1 ]++; } while (c.d[i] > 9 ) c.d[i++]%=10 , c.d[i]++; c.len = max (len, b.len); if (c.d[i] && c.len <= i) c.len = i+1 ; return c; } bign operator - (const bign& b){ bign c = *this ; int i; for (i = 0 ; i < b.len; i++){ c.d[i] -= b.d[i]; if (c.d[i] < 0 ) c.d[i]+=10 , c.d[i+1 ]--; } while (c.d[i] < 0 ) c.d[i++]+=10 , c.d[i]--; c.clean (); return c; } bign operator * (const bign& b)const { int i, j; bign c; c.len = len + b.len; for (j = 0 ; j < b.len; j++) for (i = 0 ; i < len; i++) c.d[i+j] += d[i] * b.d[j]; for (i = 0 ; i < c.len-1 ; i++) c.d[i+1 ] += c.d[i]/10 , c.d[i] %= 10 ; c.clean (); return c; } bign operator / (const bign& b){ int i, j; bign c = *this , a = 0 ; for (i = len - 1 ; i >= 0 ; i--) { a = a*10 + d[i]; for (j = 0 ; j < 10 ; j++) if (a < b*(j+1 )) break ; c.d[i] = j; a = a - b*j; } c.clean (); return c; } bign operator % (const bign& b){ int i, j; bign a = 0 ; for (i = len - 1 ; i >= 0 ; i--) { a = a*10 + d[i]; for (j = 0 ; j < 10 ; j++) if (a < b*(j+1 )) break ; a = a - b*j; } return a; } bign operator += (const bign& b){ *this = *this + b; return *this ; } bool operator <(const bign& b) const { if (len != b.len) return len < b.len; for (int i = len-1 ; i >= 0 ; i--) if (d[i] != b.d[i]) return d[i] < b.d[i]; return false ; } bool operator >(const bign& b) const {return b < *this ;} bool operator <=(const bign& b) const {return !(b < *this );} bool operator >=(const bign& b) const {return !(*this < b);} bool operator !=(const bign& b) const {return b < *this || *this < b;} bool operator ==(const bign& b) const {return !(b < *this ) && !(b > *this );} string str () const { char s[maxn]={}; for (int i = 0 ; i < len; i++) s[len-1 -i] = d[i]+'0' ; return s; } }; istream& operator >> (istream& in, bign& x) { string s; in >> s; x = s.c_str (); return in; } ostream& operator << (ostream& out, const bign& x) { out << x.str (); return out; }
/约瑟夫环
1 2 3 4 5 6 7 int ans (int n,int m) { int i,yue; for (i=1 ,yue=0 ;i<=n;i++){ yue=(yue-1 +m)%i+1 ; } return (yue); }
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 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 #include <bits/stdc++.h> using namespace std;const int N = 8005 ;int A[N];int cnt[N];struct Node { int l,r; int date = -1 ; int lazy = - 1 ; }tree[N*4 ]; int maxn = 0 ;void push_up (int rt) { if ( tree[rt << 1 ].date == -2 || tree[rt << 1 |1 ].date == -2 ) tree[rt].date = -2 ; else if ( tree[rt << 1 ].date == tree[rt << 1 |1 ].date && tree[rt << 1 ].date == -1 ) tree[rt].date = -1 ; else if ( tree[rt << 1 ].date == -1 && tree[rt << 1 |1 ].date != -1 ) tree[rt].date = tree[rt << 1 |1 ].date; else if ( tree[rt << 1 ].date != -1 && tree[rt <<1 |1 ].date == -1 ) tree[rt].date = tree[rt << 1 ].date; } void build (int rt,int l,int r) { tree[rt].l = l , tree[rt].r = r; if ( tree[rt].l == tree[rt].r ) { tree[rt].lazy = tree[rt].date = -1 ; } int mid= (l + r) >> 1 ; build (rt >> 1 , l ,mid); build (rt >> 1 ,mid +1 ,l); } void push_down (int rt,int l ,int r) { if ( tree[rt].lazy != -1 ) { int lazy = tree[rt].lazy; tree[rt << 1 ].date = lazy; tree[rt << 1 |1 ].date = lazy; tree[rt <<1 |1 ].lazy = lazy; tree[rt << 1 ].lazy = lazy; tree[rt].lazy = -1 ; } } void rangeUpdate (int rt,int l,int r,int val) { if ( l <= tree[rt].l && r >= tree[rt].r ) { tree[rt].date = val; tree[rt].lazy = val; return ; } else if ( tree[rt].l > r || tree[rt].r < l ) return ; else { int mid = ( tree[rt].l +tree[rt].r ) >> 1 ; push_down (rt,tree[rt].l,tree[rt].r); if (l <= mid) rangeUpdate ( rt<< 1 ,l ,r,val ); if ( r > mid ) rangeUpdate ( rt << 1 |1 , l, r,val ); push_up (rt); } } int Query (int rt , int l ,int r) { if ( tree[rt].l == tree[rt].r ) return tree[rt].date; else if ( tree[rt].l > r || tree[rt].r < l ) return -1 ; else { push_down (rt,tree[rt].l,tree[rt].r); int mid = ( tree[rt].l + tree[rt].r ) >> 1 ; if ( l <= mid ) return Query ( rt >> 1 , l ,r ); else return Query ( rt >>1 |1 ,l,r ); } } int ans () { int las = -1 ; for (int i = 1 ; i <= maxn ; i++ ) { int no = Query (1 ,i,i); if ( i != 1 && no == las) { continue ; } else if (i != 1 && no != las) { cnt[no]++; } las = no; } for (int i = 0 ; i <= 8000 ; i++){ if ( cnt[i] != 0 ) { cout << i << cnt[i] <<endl; } } } int main () { ios::sync_with_stdio (false ); cin.tie (0 ); int n; while (cin >> n) { maxn = 0 ; memset (cnt,0 ,sizeof cnt); int x,y,z; for (int i = 0 ; i <n ; i++){ cin >>x >> y >> z; maxn = max (x,max (y,maxn) ); rangeUpdate (1 ,x+1 ,y,z); } ans (); } return 0 ; }