pHoppz算法笔记
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
- 1____前言
- define PI 3.14
- typedef long long ll
- 1.3____特殊函数归纳
- 1.4____动态内存分配
- 1.5____加强for循环
- 1.6____STL概述
- 1.7____快读快输
- 1.8____关于取模
- 1.9____bitset基本操作
- 1.10____关于floor对小数的向下取整
- 1.11____关于stl中end(),begin(),rbegin(),rend()
- 1.12____关于取模
- 1.13____String 与 char[ ] 读入
- 1.14____String 与 char[ ] 的函数
- 1.15____任意进制转换
- 1.16____数据范围
- 1.17____cin格式化输出
- 1.18____cout格式化输出
- 1.19____ count_if()函数
- 1.20____generate() 函数
- 1.21____lambda表达式
- 1.22____再谈二进制
- 2____算法
- 3____数据结构
- SP3267 DQUERY
- 4____动态规划
- 5____图论
- 6____数论
- 7____搜索
- 8____字符串
- 9____计算几何
- 10____JAVA在ACM中的应用
- 线性筛质数,欧拉函数,莫比乌斯函数
- include 和队列基本操作相同:
- 高精度
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
宏定义注意事项
在<字符串>中的式子定义之后不会运算
,仅做字符串存储
例如:
#define N 2+2
void main()
{
int a=N*N;
printf(“%d”,a);
}
//8
宏N代表的字符串是2+2,正常计算N*N结果为16,但是最终输出为8
原因:计算的式子为 a =2 + 2*2 + 2
直接替换了N的式子
解决办法在定义式子时用“()”定义
#typedef <数据类型> <标识符>
typedef long long ll
进阶用法
c++创始人写的
typedef int P();
typedef int Q();
class X {
static P(Q); // 等价于`static int Q()`, Q在此作用域中不再是一个类型
static Q(P); // 等价于`static int Q(int ())`, 定义了一个名为Q的function
};
作用定义一个函数P( )用来表示定义一个int类型
/*** 整形 ***/
typedef int x;
x a = 0;
/*** 结构体 ***/
typedef node{
int far;
int rank;
}*Ptree , Tree , Trees[3];
Tree a1;
Trees args;
*Ptree = &a1;
//*Ptree直接定义一个指向node的指针,Tree定义一个node结构,Trees定义一个层数为3的数组
/*** 指针 ***/
typedef int *p;
p pa;
//定义一个指向int类型的指针
引用——函数传参
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;
}//cin >>5 >>6 | cout << 11
用&(引用符)传参,可直接使值直接改变到实参上
1.2.3____一些测试方法
本地读入测试数据
void file()
{
#ifdef ONLINE_JUDGE
#else
freopen( "d:/workProgram/test.txt","r",stdin );
#endif
}
int main()
{
file();
}
测试算法运行的时间
#include <time.h>
clock_t start,stop;
int main()
{
start = clock(); ///开始时间
///...算法
stop = clock(); ///结束时间
cout << "the time spend : " << (double)(stop - start) / CLK_TCK;
return 0;
}
随机数
#include <stdlib.h>
int main()
{
srand((unsigned)time(NULL));//让每次生成的随机数不一样
cout << rand() % 5 << endl; ///生成 0 - 4 之间的随机数
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) 容器查找元素
运用
指针
在数组
中实现findint 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()
-
memcpy( a,b,sizeof(int )*k )
将a数组
复制
到b数组,k为复制的大小,(int)是数组的类型,如果是直接复制整个数组就去掉*k
1.4____动态内存分配
每场比赛,基本上都要用
动态内存分配,越界验证
#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
",L[2].date);
printf("%d %d
",&L[n + 1].date,L[n].date);
return 0;
}
1.5____加强for循环
for(auto 元素 : 数据集合)
优点
:在迭代一些容器时很方便,不用写迭代器
缺点
:不能在增强循环里动态的删除集合内容、不能获取下标等。普通for循环可以没有遍历的目标,而增强for循环一定要有遍历的目标。
for (auto item : array){
cout << item << " ";
}
1.6____STL概述
stl中包含的头文件
-
三大类
container容器
algorithm算法
iterator迭代器
#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____快读快输
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); //x=x*10+ch-'0'
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;} //直到X为0 将每⼀位给s
if(!top) s[++top]=0; //特判
while(top) putchar(s[top--]+'0');
}
1.8____关于取模
- 在大数相加中,如果只是求后N位,在每次相加后取1eN的模对结果无影响
1.9____bitset基本操作
bitset<8> n;
n = 2;
cout << n << endl; ///1.用二级制输出
cout << n[1] << endl; ///2.输出n中第i位
cout << n.count() << endl; ///3.计算该二进制中1的个数
cout << n.size() << endl; ///4.输出二进制的位数
cout << n.set(2) <<endl; ///5.将n的第2位设置位1,下同
n[3] = 1;
cout << n << endl; ///6.直接赋值
n[3] = 0; ///7.直接赋值
cout << n << endl;
cout << n.reset(1) << endl; ///8.直接赋值0,同上
cout << n.set() << endl; ///9.将n的所有值设置为1
cout << n.reset() << endl; ///10.将n的所有值设置为0
cout << "/////////" << endl;
cout << n.flip() << endl;
cout << n[2].flip() << endl;
n.flip(2); ///这个好像不管用。。。
cout << n << endl; ///11.全部按位取反,单个按位取反
///12.把bitset类转换到c的数据类型ulong
unsigned long nb = n.to_ulong();
cout << nb << endl;
//bitset的方法
//any()方法如果有一位为1,则返回1
cout<<"bs1.any() = "<<bs1.any()<<endl;
//none()方法,如果有一个为1none则返回0,如果全为0则返回1
bitset<3> bsNone;
cout<<"bsNone.none() = " <<bsNone.none()<<endl;
1.10____关于floor对小数的向下取整
floor()函数不管是对int型还是double型变量向下取整后都是整数
-
对于double型的小数部分就直接丢失了
double x = 1.233
所以就要用
floor( x * 100) / 100
的方式来保留小数→ 1.23
ceil同理
1.11____关于stl中end(),begin(),rbegin(),rend()
1.12____关于取模
1.13____String 与 char[ ] 读入
对于string
:
可输入空格的一行读入
string s;
getline(cin.s);
直接读入不可输入空格
string s;
cin >> s;
对于char[ ]
:
限定大小的输入( 不能读入String )
char s[maxn];
cin.get(a,n);
cin.getline(a,n);
可输入空格一行读入
char s[maxn];
gets(s);
1.14____String 与 char[ ] 的函数
关于 string的知识点
-
可以使用 " = "进行赋值,使用 " == "进行等值比较,使用 “ + ”做串联
-
string类支持 cin , getline( cin , s ) 两种输入方式
-
string可用
s.max_size()
输出最大容量
C艹的String接口与函数
string的所有成员函数
string的其他函数
///大小写转换
transform(str.begin(), str.end(), str.begin(), ::tolower);
cout << "转小写: " << str << endl;
transform(str.begin(), str.end(), str.begin(), ::toupper);
cout << "转大写: " << str << endl;
///int 转 string
int a = 123;
string num = to_string(a);
///string 转 int
string num = "123124";
int a = stoi(num);
char 转 int
///char* || char[] 转 int
char *str = "1231245";
int a = atoi(str);
///int zh换 char[] || char*
char num[10];
int a = 213;
sprintf(num,"%d",a);
1.15____任意进制转换
#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;
///s为输入,ans为输出,te为每一轮更新的答案
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
",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格式化输出
///非科学计数法输出
cout.setf(ios::fixed);
///设置输出小数点
cout << setprecision(4) << x <<endl;
1.19____ count_if()
函数
返回满足 第三个参数这个函数的数量
#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()
函数
以第三个参数给区间赋值,第三个参数是不接受任何参数的函数对象
#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)作为参数
[] (int x) { return x % 3 == 0;}
上面count_if
的函数
bool f3 (int x ) { return x%3 ==0; }
两者的区别就是将bool f3
(返回值类型以及函数名)替换为了[]
(这就是匿名的由来);没有声明返回值类型。返回类型相当于使用 decltyp
根据推断得到的,这里为bool
。
一段简单的Code
#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++中,对于要求具有状态的函数,我们一般都是使用仿函数来实现,比如以下代码:
#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; // 发现C++11中,enum类型的使用也变了,更“强”了
return 0;
}
现在我们有了Lambda这个利器,那是不是可以重写上面的实现呢?看代码:
#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那些奇葩的东西
看以下一段代码:
#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;
}
程序输出结果如下:
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函数运行时的变量,则应该采用按引用方式进行捕捉。
再来一段更晕的代码:
#include<iostream>
using namespace std;
int main()
{
int val = 0;
// auto const_val_lambda = [=](){ val = 3; }; wrong!!!
auto mutable_val_lambda = [=]() mutable{ val = 3; };
mutable_val_lambda();
cout<<val<<endl; // 0
auto const_ref_lambda = [&]() { val = 4; };
const_ref_lambda();
cout<<val<<endl; // 4
auto mutable_ref_lambda = [&]() mutable{ val = 5; };
mutable_ref_lambda();
cout<<val<<endl; // 5
return 0;
}
这段代码主要是用来理解Lambda表达式中的mutable关键字的。默认情况下,Lambda函数总是一个const函数,mutable可以取消其常量性。按照规定,一个const的成员函数是不能在函数体内修改非静态成员变量的值。例如上面的Lambda表达式可以看成以下仿函数代码:
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的数
#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____判断奇数,偶数
if( x & 1 ){
/// 为奇数
}
else if( !(x&1) ){
/// 为偶数
}
2____算法
2.1____哈夫曼树
哈夫曼树,又名最优二叉树
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
",ans);
2.2____ 全排列
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()
对一个降序的序列进行全排列
#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
",str);
}
while( prev_permutation(str,str + len) );
return 0;
}
2.3____快速幂
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】 ()
#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 ",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 ",ans); } } return 0; }
2.5____二进制枚举
#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 <unordered_map> ///哈希表
一个序列0....4...6....13213....1e
虽然这个序列的数值非常大,但是选取的值非常
将大的数映射mapping到从0开始的自然数
这个过程就叫做离散化
vector<int> alls; // 存储所有待离散化的值
sort(alls.begin(), alls.end()); // 将所有值排序
alls.erase(unique(alls.begin(), alls.end()), alls.end()); // 去掉重复元素
//unique把重复元素放在最后,并且返回重复元素的起始位置的迭代器
// 二分求出x对应的离散化的值
int find(int x) // 找到第一个大于等于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; // 映射到1, 2, ...n
}
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 ];
前缀数组
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;
差分数组
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
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
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;
}
浮点二分
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
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 )的算法
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来代入公式计算。
输入年月日到公式,返回当天的星期数。
int week(int y, int m, int d)**///注意a[0] 为星期天,a[1]为星期一外国人的公式,安外国人的算的**
{
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;//公式
}
时分秒计算
#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())!='
')
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
",ans/3600,ans/60%60,ans%60); //时:分:秒
}
return 0;
}
2.1.13____模拟退火
模拟退火是求最优化的算法。
用于求函数最值问题,TSP旅行商问题,最小园覆盖问题,最小球覆盖问题等。
#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);
// printf("%.4lf %.4lf %.4lf
",ans,newx,next);
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
",(int)an );
}
3____数据结构
3.1____单链表
应用于后面图与树的存储
头节点(根节点)head
e[N]表示这个节点的值,ne[N]表示下一个节点的位置
3.2____树的遍历与建树
后序+中序建树 || 先序+中序建树
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倍数组)
#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;///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)==3Find(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()
==
与=
线段树是一颗完美二叉树,我们最开始输入的时候直接输入到数组
线段树可解决的问题:
- 区间最值(板子题)
- 区间和(板子题)
- 区间GCD
- 区间子段和(左边,右边,整个区间)
- 区间状态表示
- 区间开方(势能线段树)
单点更新,区间查询
建树
首先将左右节点的值初始化了之后,再去初始化根节点,递归的建树
开始我没有用struct结构来做线段树的题,但是做了更多的题之后,我发现,单纯的数组格式有很多的限制,元素与元素的传参很复杂,当指标过多的时候无法传递,所有还是改为了struct来存
///verson_单点更新
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);
}
}
///build(1,1,N);
单点修改
///verson_单点更新
///将A[pos]的值修改为val
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);
}
}
///update(1,2,5) 将A[2] = 5
区间求和
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标记下传
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存储的是当前节点区间和,所以将tag传递给孩子节点时
/// 孩子节点的值要加上区间长度乘以tag值
/// tag信息传递给孩子之后,父节点的tag信息就清空了
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;
}
}
/// 将区间[start,end]的值都加上val
/// 如果当前区间[l,r]是[start,end]的真子集
/// 那么直接修改当前节点对应的区间不用递归向下修改了
/// 记住,我们修改的值包括区间和(tree数组)和延迟标记(tag数组)
/// 否则我们需要递归查找当前节点rt的左右孩子,在进行递归查找之前我们
/// 需要先将当前节点已经存在的延迟标记传下去(push_down函数)。
/// 然后再根据左,右子树是否有目标区间的元素进行递归修改。
/// 最后不要忘记更新当前节点的值
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下传的区间查询
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____区间染色问题
#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)
{
// -2是很多颜色,-1是没有颜色,
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)
{
//cout <<tree[rt].l << ' ' << tree[rt].r <<endl;
if( l <= tree[rt].l && r >= tree[rt].r )
{
tree[rt].date = val;
tree[rt].lazy = val;
//cout << tree[rt].date << ' ' <<val <<' ' <<tree[rt].l << ' ' << tree[rt].r << ' ' <<rt <<endl;
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);
//cout << no <<endl;
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();
//for(int i = 7990; i <= 320000 ; i++)cout << tree[i].date << endl;
}
return 0;
}
3.4.3____扫描线
3.4.5____最大连续区间( 与区间最大子段和差不多 )
我现在知道结构体要开成
strcut Node
{
int l,r;
int lmax,rmax; /// 所在区间的左(右)端点为起点的最大子段和
int ma; /// 所在区间整个的最大子段和
}
/// 一个区间的lmax有两种情况
/// 1.lmax 为左儿子的lmax
/// 2.lmax 为左儿子的ans + 右儿子的lmax
/// 一个区间的rmax有两种情况
/// 1.rmax 为右儿子的rmax
/// 2.rmax 为右儿子的ans + 左儿子的rmax
/// 一个区间的ans有三种情况
/// 1.ans 为左儿子的ans
/// 2.ans 为右儿子的ans
/// 3.ans 为 ( 左儿子的rmax + 右儿子的lmax )
然后我就遇到一个问题,如何查询?
我们所查询的区间,在线段树中可能被划分为了很多个小的区间,我现在没有想通如何整合整个区间。
处理办法
多一个sum值,用来存储区间和
把
query
的return
值变为Tree
就是树的这个结构体,每次递归后return
的树,都是处理过的区间的值
假如说,我们要查询
9-12
这个区间的字段和,从(4,6,2,3)节点返回的Tree
都是处理过的,便于我们进行后序的操作。
题意:
1 - n 单点删除( 可以逐渐修复,从最后一个被删除的点开始修复 ), 问包含该点的最大连续子区间有多大
- STL做法
#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;
//int l_son = 0,r_son = 0;
//cout << *( s.upper_bound(x) ) << ' ' << *(--s.lower_bound(x) ) << endl;
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;
}
- 线段树
#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____二分+线段树
#include <bits/stdc++.h>
using namespace std;
const int N = 5e5 + 10;
struct Node
{
int l,r;
int sum,lazy; ///sum为当前剩余可插画数
///lazy = -1,为不变,lazy = 0清除插花,lazy = 1待插花
}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.4.8____势能线段树
3.5____树状数组
3.6____堆
3.7____链式前向星
什么是前向星?
前向星是一种特殊的边集数组,我们把边集数组中的每一条边按照起点从小到大排序,如果起点相同就按照终点从小到大排序
用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
链式前向星数据结构定义以及加边函数为
#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;
/// idx 为边的编号,初值为0;
/// a为起点,b为终点,w为两点之间的距离(边权)
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++){ ///head
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小(大)数的原理。
下面我们来看一道板子题
#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) ///query里的逻辑是关键点
{
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)),满足了空间要求。
#include <bits/stdc++.h>
using namespace std;
const int N =100005;
/// len为离散化之后数组的长度,a为原始数组,d为离散化之后的数组
int n,m,len,a[N],d[N];
/// T为[1,i]区间的权值线段树的根节点下标,idx为当前存到那个下标了
int T[N],idx;
struct node
{
int l,r,val;
/// 主席树的l,r与线段树不同,l存的是左儿子的节点下标,r存的是...
}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;
}
/// 添加一个数,pre为上颗树的根节点,T[0]为build的空树,T[1]为加入一个节点时的树
/// 每次都只添加一条路径(其余的连上上一颗树的节点),因为一次只增加一个点
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; ///权值线段树的特点
/// 对位相减,得到[l,r]区间的权值线段树的对应节点值
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:workProgramTypora_MarkDownHoppz算法笔记 11e225234f9f4382825a35d69f9cb19f中缀表达式.png)
#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) ) ///c,c++库函数
{
int x = 0 ,j = i;
while( j < str.size() && isdigit( str[j] ) ) ///把这个数字读取完
x = x * 10 + (int )( str[j ++] - '0' ); ///加到num栈中
i = j - 1;
num.push(x);
}
else if( c == '(' ) op.push(c);
else if( c == ')' ) ///把(...)之中的表达式从右到左计算
{
while( op.top() != '(' ) eval();
op.pop();
}
else /// 如果是符号 && 栈顶的运算符优先级大于等于自己
{ /// 则计算,如[ 1*4 + ..]到读取加号时,就先计算*
while( op.size() && pr[op.top()] >= pr[c] ) eval();
op.push(c);
}
}
while( op.size() ) eval();
cout << num.top() << endl;
return 0;
}
3.11____分块思想(数列分块)
3.12____莫队
莫队算法——从入门到黑题 - WAMonster - 博客园 (cnblogs.com)
假设 (n == m) ,那么对于序列上的区间询问问题,如果从 ([l,r]) 的答案能够(O(1)) 扩展到 ([l-1,r],[l+1,r],[l,r+1],[l,r-1](即与[l,r] 相邻的区间)) 的答案,那么可以在 (O(nsqrt{n} )) 的复杂度内求出所有询问的答案。
例1
SP3267 DQUERY
求区间有多少个不同的数
题意简明易懂:给你一个长度不大于(n≤5×10^5)的序列,其中数值都小于等于(10^6),有(m≤5×10^5)次询问,每次询问区间(l,r)中数值个数(也就是去重后数字的个数)
这题最简单做法无非暴力——用一个
cnt
数组记录每个数值出现的次数,再暴力枚举l
到r
统计次数,最后再扫一遍cnt
数组,统计cnt
不为零的数值个数,输出答案即可。设最大数值为s
,那么这样做的复杂度为$ O(m(n+s))∽O(n2)$,对于本题实在跑不起。我们可以尝试优化一下:
优化1:每次枚举到一个数值的时候,我们先判断这个数之前出现过没,如果出现过那么
ans
值就不用更新,没有出现过ans
就+1
。这样我们就优化了一个(O(ms)),但是还是跑不了的。 优化2:我们弄两个指针,每次询问不是枚举,而是移动指针到询问的区间,直到
l,r
区间与答案区间重合。在统计的时候,cnt
数组也在加减,同时ans
数组也在加减。
这样优化了之后,就有了点点莫队的雏形了,但是还不不够的,我们继续优化
莫队算法优化的核心是分块和排序。我们将大小为(n)的序列分为(sqrt{n})个块。
从(1)到(sqrt{n}) 编号,然后更具这个编号对查询的区间进行排序。
按照区间的左端点排序,如果左端点的区间相同则按右端点。
排序完后,我们再进行上述的移动左右指针的方法。
#include <bits/stdc++.h> using namespace std; /* 莫队板子 */ const int N = 1010000; /// 数据范围 int M = 1010; /// 询问次数 int a[N]; /// 输入数组 int cnt[N]; /// 这个数当前出现了多少次 int id[N]; /// 1-n 的编号,对应那个分块 int ans[N]; /// 用于输出答案 int n,m; /// n数的个数,m询问的次数 int no; /// 记录当前的ans值 int block; /// 每一个分块的大小 struct Node { int l,r; /// 存储询问的区间 int id; /// 询问输入的编号 }q[N]; int cmp(Node a,Node b) { if( id[a.l] == id[b.l] ) return a.r < b.r; return id[a.l] < id[b.l]; } void add(int pos) { if( !cnt[a[pos]] ) ++no; /// 之前没有出现过,ans++ ++cnt[a[pos]]; /// 在这个位置的这个数出现的次数 } void del(int pos) { --cnt[ a[pos] ]; if( !cnt[ a[pos] ] ) --no; /// 如果减到0了,那么这个数就没有了,ans-- } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr); int n; cin >> n; block = int(sqrt(n)); /// 读入并分块 for(int i = 1; i <= n ; i++){ cin >> a[i]; id[i] = (i - 1) / block + 1; } cin >> m; /// 读入并存储询问 for(int i = 1; i <= m ;i++){ cin >>q[i].l >> q[i].r; q[i].id = i; } /// 对询问进行排序 sort(q+1,q+m+1,cmp); int l=1,r=0; for(int i = 1; i <= m ; i++){ int ql = q[i].l , qr = q[i].r; while(l < ql) del(l++); while(l > ql) add(--l); while(r < qr) add(++r); while(r > qr) del(r--); ans[q[i].id ] =no; } for(int i =1 ; i <= m ; i++){ cout << ans[i] << endl; } return 0; }
3.13____ST表
3.13.1____解决什么样的问题?
不支持修改!
ST 表是用于解决 可重复贡献问题 的数据结构。
可重复贡献:对于运算符
x opt x = x
如
区间最大值,最小值
,区间GCD
ST表基于倍增的思想,可以做到 (O(nlogn)) 预处理,(O(1)) 回答每个询问。
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
int a[N][21];
int n,m;
inline int query(int l,int r)
{
int k = log2(r-l+1);
return max( a[l][k],a[ r-(1<<k)+1 ][k] );
}
inline void pre()
{
for(int j =1 ; j <= 21 ; j++)
for(int i = 1; i + (1<<j) -1 <= n ; i++)
a[i][j] = max( a[i][j-1] , a[i+ (1<<(j-1)) ][j-1] );
}
int main()
{
scanf("%d%d",&n,&m);
for(int i = 1 ; i <= n; i++) scanf("%d",&a[i][0]);
pre();
for(int i = 0 ; i < m ; i++){
int l,r;
scanf("%d%d",&l,&r);
printf("%d
",query(l,r));
}
return 0;
}
a[i][j]
表示的是,从i
位置开始,向后 (2^j) 个数中的最大值,例如a[i][1]
表示的是i
位置和i+1
位置中两个数的最大值
4____动态规划
取自Acwing分享
4.1____背包问题
4.1.1____01背包问题——n个东西里选任意个出来
初探dp,我觉得最重要的还是状态转移
开始听了y总
的视频课,讲真是没听懂,之后下午就一直看《挑战》
(讲真是本好书),看到书上有这样的矩阵格子,我就一步一步顺着代码,算几个数据,然后突然!我悟了!最重要的就是那几行状态转移。
01背包矩阵
外面的两层for循环,枚举每一种状态
对于dp[ i ][ j ]的解释——二维只是方便理解,真正的
-
第一层[ i ]枚举n个物品
-
第二层[ j ]枚举总总量
**这样就构建出了,选取 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个物品的体积减去,求剩下集合中选法的最大值.
const int maxn = 1010;
int n,m;///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];**
///j>=v[i] -> 当前的遍历的值小于
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
若用到上一层的状态时,从大到小枚举, 反之从小到大哦
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
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
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个放入数据中
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 };
int a[1010];
int dp[1010]; ///dp[i]表示以a[i]结尾的最大上升子序列长度
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++){
/// 把上一个的长度+1,与自己现在比较
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 ) )
记录每个长度序列的最小末尾数字,二分求解
最终代码
#include <bits/stdc++.h>
using namespace std;
typedef pair<int,int> pir;
int a[1010];
int dp[1010]; ///长度为i的最长上升子序列,末尾最小的数字
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) $ 做法
int n,m;
char a[1010];
char b[1010];
int dp[1010][1010];
///dp[i][j]表示a的前i个字母,和b的前j个字母的最长公共子序列长度
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图中不能有负权边
每次都找最短的那个点(贪心的思想)
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++){///要进行n次迭代
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);///有向图
//g[y][x] = min( g[y][x],z );///无向图
}
cout << Dijkstra() << endl;
return 0;
}
堆优化
#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短路径
5.7___spfa
Bellman-Ford的优化
处理不含负环的图,能判断是否图中存在负环
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]; // 存储所有点到1号点的距离
int cnt[N];
bool st[N]; // 存储每个点的最短距离是否已确定
// 求1号点到n号点的最短距离,如果不存在,则返回-1
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
#include <bits/stdc++.h>
using namespace std;
const int N = 510,M =10010;
int n ,m,k;///最多只能走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++){///走k次
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思路做
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");
//由于有负权边存在所以约大过INF/2也很合理
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必有拓扑序列
再排完序之后所有的边都是从前指向后的。
把所有入度为0的点入度
枚举
no
的所有出边(每遍历一个边就删掉一个边,indegree--) 如果 indegree == 0 了那就让这个点入队
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5;
struct Node
{
int to,next;
}e[N];
int h[N],n,m,idx,in[N],ans[N],cnt;
void add(int u,int v)
{
e[idx].to = v;
e[idx].next = h[u];
h[u] = idx++;
}
void bfs()
{
queue<int> q;
for(int i = 1; i <= n ; i++){
if( in[i] == 0 ) q.push(i);
}
while(!q.empty()){
int no = q.front();
q.pop();
ans[cnt++] = no;
for(int i = h[no] ; i != -1; i = e[i].next){
int j = e[i].to;
if( --in[ j ] == 0 ) q.push( j );
}
}
if( cnt == n ){
for(int i = 0 ; i < n ; i++){
cout << ans[i] << "
"[i == n-1];
}
}else cout << -1 << endl;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
memset(h,-1,sizeof h);
cin >> n >> m;
for(int i = 0 ; i <m; i ++){
int a,b;
cin >> a >> b;
add(a,b);
in[b]++;
}
bfs();
return 0;
}
反向建图
#include <bits/stdc++.h> using namespace std; const int N = 2e4 + 10; int n,m; struct Node { int next,to; }e[N]; int h[N],out[N],idx; void add(int u,int v) { e[idx].to = u; e[idx].next = h[v]; h[v] = idx++; } void bfs() { queue< pair<int,int> > q; int cnt= 0 ,sum=0; for(int i = 1 ; i <= n ; i++){ if( out[i] == 0 ){ q.push( {i,888} ); } } while( !q.empty() ){ auto no = q.front(); q.pop(); sum += no.second; cnt++; //cout << no.first << ' '<< cnt << ' ' << sum << endl; for(int i = h[no.first] ; i != -1; i = e[i].next){ int j = e[i].to; if( --out[ j ] == 0 ) q.push( { j,no.second+1 } ); } } if( cnt == n ) cout << sum <<endl; else cout << -1 << endl; } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr); while(cin >> n >> m ){ memset(h,-1,sizeof h); memset(out,0,sizeof out); memset(e,0,sizeof e); idx = 0; for(int i = 0 ; i < m ; i++){ int a,b; cin >>a >> b; add(a,b); out[a]++; } bfs(); } return 0; }
6____数论
数论基础知识点
- 对于一个合数,他最小的那个因子一定是个质数
6.1____中国剩余定理
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____欧拉函数线性筛
筛法求欧拉函数
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____扩展欧几里得算法
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;
}
bool topsort()
{
int hh = 0, tt = -1;
// d[i] 存储点i的入度
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____分解质因数
试除法
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____求素数
快速判断一个数是不是素数
bool is_p( int num )
{
if(num ==2|| num==3 ) //两个较小数另外处理
return 1 ;
if( (num %6!= 1&&num %6!= 5) || num == 1) //不在6的倍数两侧的一定不是质数
return 0 ;
for(int i= 5; i <= num / i; i+=6 ) //在6的倍数两侧的也可能不是质数
if(num %i== 0||num %(i+ 2)==0) //排除所有,剩余的是质数
return 0 ;
return 1 ;
}
朴素筛法-O(nlogn)
st[N]判断是否为素数,prime储存筛出来的质数
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)
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仅会被其最小质因子筛去
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;///只用最小质因子筛,等于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也为pji最小质因子
6.6____求约数——通用试除法
约数的定义:整数a 除以 整数b(b≠0) 除得的 商 正好是整数而没有余数 a称为b的倍数,b称为a的 约数
试除法求一个数的约数( O(n*sqrt(a)) )
/* 求这个n的所有约数,返回一个存有约数的vector */
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;
}
求约数个数
/* 给定n个正整数ai,请你输出这些数的**乘积**的约数个数,答案对 1e9+7 取模。 */
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;
}
求约数和
/* 给定n个正整数ai,请你输出这些数的**乘积**的约数之和 , 答案对 1e9 + 7 取模 */
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;///first为这个约数,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互质的数的个数
/* 给定n个正整数ai,请你求出每个数的欧拉函数。 */
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了!!!
#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 魔板
#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; ///8位数最多只有40320种组合
pair<int,int> pre[maxn];
int st[maxn];
struct node{ int a[8]; int root}; ///存路径
int main()
{
bfs
return 0;
}
AcWing 845 八数码
7.4____双向搜索
双向BFS
双向同时搜索的基本思路是从状态图上的起点和终点同时开始进行 广搜 或 深搜。如果发现搜索的两端相遇了,那么可以认为是获得了可行解。
#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 ){
//cout << dis[no.x][no.y] << '&' << no.cnt <<endl;
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的康托展开值,也就是:(0*4!+ 0*3!+ 0*2!+ 0*1!+0*0! = 0)
前10阶乘( 0 - 9)
1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880
/* 康拓展开的代码 */
#include <bits/stdc++.h>
using namespace std;
int facnum[20]; ///存前20个数的阶乘“从0开始”
///求阶乘
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 ++; ///计算在str[i]之后有几个比他小的数
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
/* 康拓逆展开的代码 */
#include <bits/stdc++.h>
using namespace std;
int facnum[20]; ///存前20个数的阶乘“从0开始”
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 --; ///安12345为第0位开始计算;
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;
///康托展开
///* 如果给定了是几位数的全排列则不需要这一段,直接num = 位数就好
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))
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))
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暴力枚举做法
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开始)
char p[maxn],s[maxm]; ///p为模板数组,s为原数组
int ne[maxn]; ///next数组
int n,m;///n为模板
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是变为0了,在模板串中退无可退了,s[i] != p[j + 1]是匹配到不相同的位置了
while (j && s[i] != p[j + 1]) j = ne[j];
if (s[i] == p[j + 1]) j ++ ;
if (j == n){
// 匹配成功后的逻辑
j = ne[j];///注意!!!这里等于0,和等于ne[i] 匹配成功后回跳还是不回跳会对结果有影响
}
}
}
/// s[]是长文本,p[]是模式串,n是s的长度,m是p的长度
//求模式串的Next数组:
int main()
{
cin >> n >> p + 1 >> m >> s + 1;
get_next();
KMP();
return 0;
}
算法模板(从0开始)
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]) 对所有$ iepsilon [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数组,然后匹配所有其他的串
#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))
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];
}
}
const int N = 2e7 +10;
int q,ne[N],ex[N]; /// ne为t与自己的后缀数组,ex为s与t的后缀数组
int slen,tlen; ///匹配串与模板串长度
char s[N],t[N]; ///匹配串,模板串
void get_next()
{
ne[0] = tlen; ///ne[0]一定是T的长度
int now = 0;
while(t[now] == t[1 + now] && now + 1 < tlen) now++;///从1开始暴力枚举第一位
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]; /// k + l < p
else{
int now = ne[p0] + p0 - i;
now = max(now, 0); ///防止i > p 的情况
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
算法思路
typedef unsigned long long ull;
const int N = 100010, M = 131;
ull p[N],h[N]; ///p用来存M的次方
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字典树
高效的存储和查找字符串的数据结构
int son[N][26], cnt[N], idx;
// 0号点既是根节点,又是空节点
// son[][]存储树中每个节点的子节点
// cnt[]存储以每个节点结尾的单词数量
// idx当前用到那个下标
// 插入一个字符串
void insert(char *str)
{
///p相当于一个指针,下同
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))
分别计算奇偶
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;
预处理版本
#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 - 2ac*cos(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____板子
const double eps = 1e-8;
const double pi = acos( -1.0);
///Compares a double to zero
int sgn(double x)
{
if( fabs(x) < eps ) return 0;
if( x < 0 ) return -1;
else return 1;
}
///square of a double
inline double sqr(double x) { return x * x; }
/////////////////////////////////////////////////
struct Point
{
double x,y;
Point(){} ///no arguments constructor
Point(double _x,double _y) {
x = _x , y = _y; ///arguments constructor
}
/*void input(){
scanf("%lf%lf",&x,&y);
}
void output(){
printf("%.2f %.2f
",x,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); ///<cmath>
}
///长度的平方
double len2(){
return x * x + y * y;
}
///返回两点的距离
double distance(Point p){
return hypot( x - p.x , y - p.y );
}
///计算 pa 和 pb 的夹角
double rad(Point a,Point b){
Point p = *this;
return fabs( atan2( fabs( (a-p)^(b-p) ) , (a-p)*(b-p) ) );
}
///化为长度为r的向量
Point trunc(double r){
double l = len();
if( !sgn(l) ) return *this;
r /= l;
return Point(-y,x);
}
///逆时针旋转90度
Point rotleft(){
return Point(y,-x);
}
///顺时针旋转90度
Point rotright(){
return Point(y,-x);
}
///绕着p点逆时针
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; }
///由斜倾角angle与任意直线一点确定直线 y = kx + b;
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)));
}
///ax + by + c = 0;
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);}
///直线与线段相交判断
///-*this line -v seg
///2规范相交,1非规范相交,0不相交
bool linecrossseg(Line v){
return sgn( (v.s - e) ^ (s - e) ) * sgn(( v.e-e ) ^ (s -e) ) <= 0;
}
///点与直线关系
///1在左侧
///2在右侧
///3在直线
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;
}
///两直线关系 0-平行,1-重合,2-相交
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();
}
/// 返回点p在直线上的投影
Point lineprog(Point p){
return s + ( ( (e-s)*((e-s)*(p-s)) ) / ( (e-s).len2() ) );
}
///两线段相交判断
///2 规范相交
///1 非规范相交
///0 不想交
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;
}
/// 点与圆的关系
///0 在圆外
///1 在圆上
///2 在圆内
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;
}
/// 求圆和三角形 pab 的相交面积
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;
}
};
///极角排序
///mi为最左下角的点
void norm(){
Point mi = p[0];
for(int i = 1; i < n; i ++) mi = min(mi,p[i]);
sort(p, p + n, cmp(mi) );
}
/// 判断任意点与多边形的关系
/// 3在顶点上
/// 2在边上
/// 1在内部
/// 0在外面
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;
}
/// 得到凸包
/// 得到的凸包里的点编号是 0 ~ n-1 的
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);
}
///多边形和圆的关系
/// 2圆完全在多边形内
/// 1圆在多边形里面,碰到了多边形边界
/// 0其他
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;
}
};
快乘与快速幂
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;//a * a不能爆掉
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);
类似C语言中的输出
2.定义变量
-
int a,b,c
一个元素的定义和c中无差别 -
BigInteger a;
定义大整数类 -
int []a = new int[1000]
定义数组 -
BigInteger big[] = new BigInteger[1005];
定义大整数数组 -
BigDecimaln big;
大整数浮点类
BigInteger任意大的数,原则上只要你的计算机内存足够大,可以有无限位
10.2____BigInteger大整数
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); /// a = c + b;
b = a.subtract(c); /// b = a - c;
c = b.multiply(a); /// c = a * b;
a = c.divide(b); /// a = c / b;
///取余 || 取模
a = c.remainder(b) == c.mod(b) /// a = c/b的余数
///最大公约数
c = a.gcd(b); /// c = a,b的最大公约数
///比大小
c = a.max(b);
c = a.min(b);
///判断相等,不能用 '=='
if( a.equal(b) ){...}
10.3____BigDecimal大浮点数
10.3.1____浮点大数的格式化输出
DecimalFormat df = new DecimalFormat("0");///整数输出
///如果是("0.00")就是保留两位小数输出
BigDecimal big;
Scanner cin = new Scanner( System.in );
big = cin.nextBigDecimal();
big = big.setScale(0, BigDecimal.ROUND_DOWN);
///输出的格式要调用 format函数输出
System.out.println( df.format(big) );
/*在对浮点大数初始化的时候,如果不是输入的话最好用下面这个形式
*DecimalFormat df = new DecimalFormat("3.432354324");
*而不是用
*DecimalFormat df = new DecimalFormat(3.432354324);
*这种方式可能会造成精度缺失,而以字符串的形式就不会
*/
10.3.2____浮点大数的setscale方法()
BigDecimal big = new BigDecimal("2.624124");
BigDecimal a = new BigDecimal("0");
/* a = big.setscale(重哪一位开始处理,处理的模式)
* a = big.setscale( 1,BigDecimal.ROUND_DOWN) 重第2位开始
* 处理进位到1位
* ROUND_DOWN 去掉多余的位数,不管后面数字的大小
* ROUND_UP 进位处理,不管后面数字的大小
* ROUND_CEILING 正数==ROUND_UP负数==ROUND_DOWN
* ROUND_FLOOR 正数==ROUND_DOWN负数==ROUND_UP
* ROUND_HALF_UP 根据后面的数字四舍五入(大于等于 5进位)
* ROUND_HALF_DOWN 根据后面的数字四舍五入(大于 5进位)
*/
快读与快输
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); //x=x*10+ch-'0'
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;} //直到X为0 将每一位给s
if(!top) s[++top]=0; //特判
while(top) putchar(s[top--]+'0');
}
线性筛质数,欧拉函数,莫比乌斯函数
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
-
DFS
-
DFS例题棋盘游戏(有多个可放棋子的位置,每个棋子会与其他相同x轴y轴相冲突,求最多可放多少个棋子)
char s[25][25]; int ans,n,m; bool a[20]; //a[10]==true 表示y==10的位置有棋子 void dfs(int i ,int sta){ if(sta==m){ // ans++; return; } if(i>=n) return; //避免越界错误 int j ,en; for(j = 0 ; j < n ;j++){ if(s[i][j]=='#'){ //可以放的位置 if(!a[j]){ a[j]=1; dfs(i+1,sta+1); a[j]=0; } } } dfs(i+1,sta); //这行的放与不放 } void solve(){ int i,j; while(scanf("%d%d",&n,&m)){ if(n==-1&&m==-1){ break; } memset(a,0,sizeof(a)); for(i = 0; i < n ; i++){ scanf("%s",s[i]); } ans=0; dfs(0,0); cout<<ans<<endl; } } /*不能重复经过同一个点的dfs*/ bool vi[maxn][maxm]; void dfs(int x , int y , int k){ if(k == maxn){//当满足条件时的特定操作,这里是找到操作就给ans加一 ans++; return; } for(int i = 0;i < 4; ++i){ int ex = x + dx[i]; int ey = y + dy[i]; if(!vi[ex][ey]){ vi[ex][ey] = 1; dfs(ex, ey , k + 1); //满足条件后对k进行操作 vi[ex][ey] = 0; } } }
-
BFS
-
BFS 列题 胜利大逃亡(从‘@’位置到‘^’位置的最短时间不能超过t 且‘A’墙需要有钥匙‘a’才能通过 钥匙有a—j一共十把 对应墙A—J)
int inf = 2147483647; int dp[1500][25][25]; //第一维表示拥有的钥匙种类 char s[25][25]; int dx[4]={0,0,1,-1},dy[4]={1,-1,0,0}; typedef struct{ int x , y , key ; //用key表示有a--j那把钥匙 如5==101 表示有钥匙‘a’与‘c’ }sp; void solve(){ int n , m , t , ans; int stx , sty , enx , eny; while(cin>>n>>m>>t){ queue<sp> qu ; for(int i = 0 ; i < n ; i++){ for(int j = 0 ; j < m ; j++){ scanf(" %c",&s[i][j]); if(s[i][j]=='@') stx = i , sty = j; //明确BFS起点与终点 else if(s[i][j]=='^') enx = i , eny = j; } } for(int i = 0 ;i<1500;i++) for(int j=0 ;j<25;j++) for(int k = 0 ; k<25;k++) dp[i][j][k]=inf; //memset函数只能通过字节重置 ans = inf; sp a ; //bfs的初始化 a.x = stx,a.y = sty , a.key = 0; qu.push(a); dp[0][stx][sty] = 0; while(!qu.empty()){ sp te = qu.front(); qu.pop(); if(te.x == enx && te.y == eny){ ans = dp[te.key][te.x][te.y] ; break; } for(int i = 0 ; i < 4 ; i++){ sp ae; ae.key = te.key; ae.x = te.x + dx[i]; ae.y = te.y + dy[i]; if(ae.x >= 0&&ae.y>=0&&ae.x<n&&ae.y<m&&s[ae.x][ae.y] != '*'){ if(s[ae.x][ae.y]>='A'&&s[ae.x][ae.y]<='J'&&!(ae.key&(1<<(s[ae.x][ae.y]-'A')))){ continue; } if(s[ae.x][ae.y] >= 'a' && s[ae.x][ae.y] <='j'){ ae.key = ae.key | (1<<(s[ae.x][ae.y]-'a')); //更新拥有的钥匙种类 } if(dp[ae.key][ae.x][ae.y]!=inf) continue;//一个点只会被更新一次 dp[ae.key][ae.x][ae.y] = dp[te.key][te.x][te.y] + 1; qu.push(ae); } } } if(ans>=t) cout<<-1<<endl; else cout<<ans<<endl; } }
带权并查集与种类并查集
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; //有1000个物品分为m类
int a[1000 * m + 5] //用表示m个种类
单调队列解决固定区间最大最小问题
int l = 0 , r = 1;
dp[0] = 1;
if(m==1) printf("%d
",a[1]); //特判
for(int i = 2 ; i <= n ; i++){
if(i-du[l] >=m && l<r) l++; //保证i与du[l]的区间差值不超过m
while(r>l&&a[du[r-1]] >= a[i]) r--; //更新a[i]应该在的位置
du[r++] = i;
if(i >= m) printf("%d",a[du[l]]);
}
RMQ问题与st数组、线段树
/*ST数组 离线处理 O(NlogN) 查询O(1) */
int m,s[10000],n,dp[10000][500],en; //dp[i][j]表示从i到i+1<<j-1的最值
void rmq_st(int n ){
memset(dp,0x3f,sizeof(dp)); //赋初值
for(int i = 1; i <= n ; i++) dp[i][0] = s[i]; //对应每个s【i】到 i+2^0-1对应它本身
m = (int) (log(1.0 * n) / log(2.0)); //区间可以分多少次
for(int j = 1 ; j <= m ; j++){
int t = n - (1<<j) + 1; //t就是里面最后的区间中l的位置
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("
");
}
}
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; //将l--r的距离分成俩快 必被l--l+2^k-1
//与l+2^k--l+2^(k+1)-1
}
}
/*线段树 预处理O(NlogN) 在线处理O(logN) 查询O(logN)*/
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);
//pushdown(p,l,r);
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){
//dp[p]+=lazy[p]*(r-l+1);
//pushdown(p,l,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次即存在负圈
typedef struct{
int v,cost;
}pe;
int dp[1005],n,m,cn[1005]; //dp数组记录从起点到各个终点的最短距离 cn表示每个点的入度次数
bool vi[1005];
int flag; //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;
}
链式前向星( 迪杰斯特与佛洛依德算法里 有其他俩种存储方式)
typedef struct{
int to , next , cost;
}sp;
int head[1004]; //head【i】 表示从i到其他点最后一条表的位置
sp cnt[1004];
void add(int u,int v,int w){
cnt[top].to = v;
cnt[top].cost = w;
cnt[top].next = head[u]; //将next所指变成head【x】的值
head[u] = top++; //更新head【x】位置 如果是单向边可以直接写成i
}
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;
}
}
}
**分块思想 (将一个序列分成几个小块)**
用树状数组 线段树 的题用这个也可 处理 区间加 乘 求和 查询 单点查询
int n,a[100],pos[100],add[100],d; //add数组表示一个块内增加的总数 pos数组代表第几个
位置在第几个块
void add_1(int l,int r,int c){ //区间加
for(int i = l; i <= min(r,pos[l]*d); ++i)
a[i]+=c;
//sum[pos[l]]+=c;
if(pos[l] != pos[r]){
for(int i = (pos[r]-1)*d+1; i <= r; ++i)
a[i]+=c;
//sum[pos[r]]+=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); //总共分qrt(n)个块
for(int i=1;i<=n;++i)
cin>>a[i];
for(int i=1;i<=n;i++)
pos[i]=(i-1)/d+1; //pos数组初始化
//sum[pos[i]]+=v[i]; //求和数组初始化
for(int i=1;i<=n;++i){
cin>>opt>>l>>r>>c;
if(opt) cout<<a[r]+add[pos[r]]<<endl; //单点查询
// cout<<query(l,r)<<endl; 区间求和
else add_1(l,r,c); //区间加
}
return 0;
}
欧拉回路
无向图判断
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; // 用数组 stk 来模拟一个栈
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; // 将顶点 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
/*假设k为a的逆元 先讲ax mod m = 1 等价于 ax - my = 1 然后通过exgcd可以得到一组x,y(前提是
gcd(a,m)== 1 再将x取值到0——m的范围内 即(x+m)% m)
int exgcd(int a, int b, int &x, int &y) {
if(b == 0) {
x = 1;
y = 0;
return a;
}
int r = exgcd(b, a % b, x, y);
int t = x; x = y; y = t - a / b * y;
return r;
}
优雅暴力——莫队
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];
}
/* bool cmp(sp a, sp b){
return (belong[a.l] ^ belong[b.l]) ? belong[a.l] < belong[b.l] : ((belong[a.l] & 1) ?
a.r < b.r : a.r > b.r);
} 玄学奇偶性排序 */
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); //排序 不同块按顺序排 同块按r排序
for(int i=1;i<=m;i++)
{
int q1 = ans[i].l , q2 = ans[i].r;
while(l < q1) del(l++); //l<p1 计算了l点 要计算l--p1(不含p1 含l) 所以l++
while(l > q1) add(--l); //l>p1 计算了l点 要计算l-1--p1(不含l 含p1) 所以--l
while(r < q2) add(++r);
while(r > q2) del(r--);
ans[Q[i].id]=Ans; //将当前所得的值送给应在的位置
}
字典树(Trie树)
const int maxn=10005;
int dp[maxn][26];
int head[26],top; //head表示每一个首字母所指的位置
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
",query());
}
}
KMP(字符串匹配)
解决 判断a是否为b的子串(连续) 时间复杂度为O(N)
#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(){ //Next[j] 存储a[i]!=b[j]时 j要跳转的位置
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;
}
二分图
#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) //如果颜色重复,就返回false
return false;
if(color[i] == 0 && !dfs(i,-c)) //如果还未涂色,就染上相反的颜色-c,并dfs这个顶点,进入下一层
return false; //返回false
}
}
return true; //如果所有顶点涂完色,并且没有出现同色的相邻顶点,就返回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];//获得第i个关联点的下标
if(col[x]==0)//如果没图色
{
col[x]=-col[v];//图相反颜色
q.push(x);
}
else
{
if(col[x]==col[v])//颜色相同不是二分图返回false
return false;
}
}
}
return true;
}
int main(){
solve();
return 0;
}
最小生成树
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
",ans);
tarjin算法
int dfn[maxn],low[maxn],top = 0,k = 0;//dfn记录当前点的时间戳 low即可以构成强连通分量中时间
戳最早的点的dfn top即时间戳 k表示有几个强连通分量
int cn[maxn],cnt[maxn];//cnt记录每个强连通分量拥有分量个数
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]);//如果te点可以构成强连通分量且这个点的时间戳小于x的
时间戳则更新这个点的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); //解决节点入度为0的情况
数位dp
int dp[20][2],a[20];//a数组存数位分解之后的边界 //【0】【1】表示特殊状态
int dfs(int pos,int sta,bool limit){ //pos表示枚举第几位 sta表示特殊状态 limit限制最高的位
if(pos==0) return 1;
if(dp[pos][sta]==-1||limit){ //只有limit不存在且dp【pos】【特殊状态】搜索过可以直接记忆化
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
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]);//每个合理的数如果与当前行匹配 就加入a[i]
}
}
}
优先队列
include 和队列基本操作相同:
top 访问队头元素
empty 队列是否为空
size 返回队列内元素个数
push 插入元素到队尾 (并排序)
emplace 原地构造一个元素并插入队列
pop 弹出队头元素
swap 交换内容
priority_queue <int,vector
//降序队列,大顶堆
priority_queue <int,vector
//greater和less是std实现的两个仿函数(就是使一个类的使用看上去像一个函数。其实现就是类中实现一个operator(),这个类就有了类似函数的行为,就是一个仿函数类了)
①基本类型优先队列的例子:
②用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 << '
';
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
//获取字符串长度
**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;**
博弈论
巴什博伊:只有一堆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;
}
}
//Nim取石子 有n堆石子,第i堆有A(i)颗石子。两人依次从中拿取,规定每次只能从
一堆中取若干根,可将一堆全取走,但不可不取,最后取完者为胜
int main(){
int a=0;
cin>>n;//有n堆石子
for(int i=0;i<n;++i){
cin>>m;//m代表当前堆有多少个石子
a^=m;
}
if(a) 先手赢 //a>0为利己态
else 后手赢 //a=0为利他态
}
高精度
正整数间的加 乘 除 余 减 (减法结果不能为负数)
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;
}
/约瑟夫环
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);
}
#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)
{
// -2是很多颜色,-1是没有颜色,
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();
//for(int i = 1; i <= 1000 ; i++)cout << tree[i].date << endl;
}
return 0;
}