文章目录
一 、零碎知识
cin,cout速度会慢一点,如果输入
#include<iostream> //C++标准输入输出,cin/cout
#include<cstdlib>
#include<cstdio> //c标准输入输出,scanf/printf
#include<algorithm> //常用算法,比如快速排序sort函数
#include<map><vector><string><set> //STL相应模块
#include<cstring> //c语言字符串相关,memset函数所在头文件
#include<cctype> //字符转化相关
1.5.2的练习都要
一般时间复杂度超过10的九次方,一定就超时了
数据范围和时间复杂度相关,如果数据是int 10的六次方就超过内存了
每个case出一个结果是没有关系的,尽量不用指针和动态申请内存。
链表用c语言实现
申请内存空间用
#include<malloc.h>
ascii码值
空格 32
0-9 48-57
A-Z 65-90
a-z 97-122
memset(数组名,0/-1,sizeof(数组名))
通常只有复制0和-1的时候可以初始化整个数组
记得写多个case的题目的时候,记得每次先初始化
1.一棵树的存储:
(1)三个数组:
left[x]是节点x的左孩子;
right[x]是节点x的右边孩子;
father[x]是x的父节点
A-Tree Recovery
前序遍历S 中序遍历T
S[i]~S[i+k-1] T[j]~T[j+k-1]
DBACEGF ABCDEFG
void my_print(int i,int j,int k) //i,j起始位置,k长度
{
if(k==0) return;//空树
//找到S[i]在T中的位置p;
char root=S[i];
int p=j;
while(T[p]!=root) p++;
//递归建树
if (p-j >0)
my_print(i+1, j , p-j); //打印左子树
if (j+k-1-p>0)
my_print(i+1+p-j , p+1, j+k-1-p); //打印右子树
printf(根节点S[i])
}
打印左子树的后序
打印右子树的后序
打印根节点
B - Red and Black
int vis[maxn][maxn]
dfs(i,j) //从@开始dfs
for()遍历vis
while(scanf("%d%d",&n,&m) && (n||m))
{
//初始化vis
//读入图
//solve
}
带边权存图
vector<Edge> G[maxn];
for(int i=0;i<G[u].size();i++)
{
int v = G[u][i].to;
int d = G[u][i].dist;
}
x,y,d
Edge e;
e.from=x;e.to=y;e.dist=d;
G[x].push_back(e);
e.from=y;e.to=x;e.dist=d;
G[y].push_back()
最小生成树
//定义边
typedef struct Edge
{
int from,to,dist;
}Edge;
vector<Edge> edges;
//对边按照权值排序
bool compare(Edgee1,Edge e2)
{
return e1.dist<e2.dist;
}
sort(edges.begin(),edges.end(),compare);
// kruskal算法
int res=0;
for(int i=0;i<edges.size();i++)
{
//边是edges[i]
int a=edges[i].from,b=edges[i].to;
//判断是否能加入
if(find(a)!=find(b))//不在一个集合=>不成环=>可以加入最小生成树
{
res+=edges[i].dist;
//合并
//如果是优化后的find,已经在find的时候将ab的父节点改成树根了
//合并过程:
int pa=parent[a],pb=parent[b];
parent[pb]=pa;//直接把b所在的树接到a所在的树上
//如果是没有优化的find,合并过程:
uion(a,b);
}
}
并查集
初始每个节点都是一棵独立的树,int parent[x]=x;//初始化
- 优化前
int find(int x) //找x属于哪一棵树就是找x的根节点
{
int p=parent[x];
if(p==x) retuen x; //如果x是根节点直接
else return find(p); //如果x不是根节点就继续向上找
}
void uion(int x,int y)
{
int px=find(x); //找到x,y的根节点
int py=find(y);
parent[py]=px; //把y所在的树合并到x所在的树上
}
会退化成链,导致时间复杂度变成O(n)
- 优化后
//查找x属于哪一个集合(或者说哪一棵树)
int find(int x)
{
if(parent[x]==x)
return x;
else
{
int p= find(parent[x]);//不断递归直到找到根节点parent[p]==p
parent[x] = p; //优化
// d递归返回的过程中把这一个分支路径上所有点的父节点都改成了根节点
return p;
}
}
//合并
void uion(int x,int y)//没有变化
{
int px=find(x);
int py=find(y);
parent[py]=px;//把y所在的树接到x所在的树上了,
//让y的根节点的根节点是x的根节点,然后find的过程会优化
}
//判断是否在一个集合
if(find(x)==find(y) )
2.DFS/BFS
DFS
先写main()函数,把输入输出等定义完,再去写dfs函数
过程
main()
{
遍历图
if(符合要求的点) dfs(点的坐标,[连通块计数])
}
dfs(点的坐标,[连通块计数])
{
判断是否越界
判断是否访问过&&符合要访问的点的要求
标记为已访问(用1或连通块计数)
[ 统计该连通块子块数量++]
几个方向相邻点:dfs(点的坐标,[连通块计数])
}
int pic[maxn][maxn];//二维数组存无向图
int m,n,idx[maxn][maxn];//mn是图的边界,idx用于标记这个点是否访问过,未访问一般是0,访问过是1(如果统计连通分量,就用于记录是第几个连通分量)
map<int,int> sub_cnt;//key=连通块编号,value=这个连通块的子块数量
int main()
{
//如果要判断有几个连通块就用cnt计数:int cnt=0;
memset(idx,0,sizeof(idx));//初始状态所有点都未访问过
//对整个图遍历
for(int i=0;i<m;i++)
for(int j=0;j<n;j++)
{
if(idx[i][j]==0&&pic[i][j]=='@'))//未访问 且 满足要找的点的条件
{
cnt++;
//sub_cnt[cnt]=1;:统计该连通块的子块数量
dfs(i,j,cnt);//第三个参数是统计连通分量的
}
}
}
void dfs(int r,int c,int id)
{
if(r<0||r>m||c<0||c>n) return;//判断这个点是否越界. 因为后面会用二维数组对这个点上下左右各方向相邻的点递归dfs,如果(r,c)恰好是边界点,那么它的上下左右就可能越界
if(idx[r][c]>0||pic[r][c]!='@') return;//访问过 或 不满足要找的点条件
idx[r][c]=id;//标记这个点为已访问
sub_cnt[id]++;
for(int dr=-1;dr<=1;dr++)//递归上下左右的点
for(int dc=-1;dc<=1;dc++)
if(dr!=0||dc!=0) dfs(r+dr,c+dc,id);//因为是相邻点,所以是同一个连通分量
}
BFS
一般会定义一个和图相同维度的二维数组,用于记录这个点是否已经入队(走过),在判断这个点能否可走的时候用到
G[maxn][maxn];//存图
idx[maxn][maxn];//标记点是否已经入队(是否访问过)
//dfs中也有这样额标记数组
一般BFS要记录一下:
(1)这个点走到画了多少步,
(2)一切其他信息,
有时候会定义一个结构体来存储用BFS遍历的这个图的当前节点需要的信息
queue<node>
typedef struct {
点的位置(坐标)
到这个点的时候走了多少步
}node;
注意 这样定义决定了增加步数的位置是在节点入队的时候
如果将步数用一个单独的变量step存储,表示当前的步数,那么步数更新是在遍历完当前队列中的所有节点后
过程
初始节点入队
while(!q.empty())
{
for 节点 in 队列//队列中的所有点依次出队
{
队首出队: node=q.front(); q.pop();
if(到终点) return ;//用BFS求最短路的时
遍历node相邻节点
if(满足可以走的条件) {
这个相邻点的step=node.step+1;
这个相邻点入队;
标记已经走过;
}
}
}
可以走的条件,包括:
题目中给出的限制
是否已经访问过
。。。
q.push(元素
入队 q.empty()
是否空 q.front()
取队首的值 q.pop()
弹出
上下左右移动,一般声明一个数组来实现
int dx={0,0,-1,1};
int dy={-1,1,0,0};
(i+dx,j+dy)//是(i,j)的左、右、下、上
固定一种方式实现
二、存图方式
1.二维数组
一般图都存成二维数组就可以,有向图另说
2.有向图
(1)vector存图
vector<类型> G[100];
//相当于是二维数组
图有100个节点,G[i]存的是所有从以点i为始点的点的下标
实现方式如下:
读入一条边:a->b,把b塞入a的vector里面,G[0].push_back(1)
可以用map来映射节点和它的下标
map<char,int>point
point['a']=0;point['b']=1;
(2)二维数组
如果节点是符号,那么定义一个数组存
char node[maxn];//node[i]表示第i个点对应的符号,eg:node[0]=A;node[1]=B;
G[maxn][maxn];
//如果G[0][1]=1表示有一条从点A指向点B的边,否则G[0][1]=0
3.带权图
定义结构体
算法书:P360
三、拓扑排序
思路一:
可以用一个队列queue
来存储入度为零的点,每次从队列取出队首作为这次拓扑要访问的点
在访问一个节点时候,它指向的点入度减一,判断如果为0,压入队列
思路二:
用dfs实现
见算法P168
四、多层循环的时候可以用return 代替break
五、输入输出
C++:while(cin>>s)
C:while(if(scanf("%d",&a)!=EOF))
相当于用EOF作为输入的结尾
以上两种不用终止EOF结束就不会跳出while
保留两位小数:
printf(".2f\n",a);
short/int : %d
long: %ld;
(long 是int得修饰,不能算是一种单独的数据类型,只是比int多了四个字节的存储空间)
long long: %lld
char : %c
float/double : %f
float默认是6位小数输出;可以在%f中控制;例如:%.2f:输出两位小数;
char *s(字符串) :%s
unsigned: %u
(signed:有符号类型, unsigned:无符号类型;默认都是有符号的)
八进制:%o
以0开头
十六进制:%x
以0x开头
六、没有输出时调试思路
当没有输出的时候,调试思路:
1.死循环了
2.不满足return的条件
比如在main()函数的BFS后输出一个符号,看一下bfs跳出了吗
如果BFS没有跳出,那么就是可能是判断压入队列的条件有问题,或者标记值没有更新啦
七、最短路
BFS:广度优先搜索;可以走的点是x+1,x-1,2x
拓扑排序
是不是访问过,一般开一个数组存放,而不会放在BFS的结构体
BFS的过程:
初始点入队
判断队列不为空
{
出队一个点
遍历这个点的所有相邻的点,判断这些点是否可以走,把这些点的内容算出
把这个点入队
}
如果求任意两点之间的最短路,且n的范围n<1000的时候,可以在1s内计算出,如果n>1000的时候,弗洛伊德算法肯定会超过一秒
floyd算法
1.k的循环必须在外面
2.很多暴力求解都可以用,只要改换里层逻辑即可
3.可以加上一个最大值define INF 1000001,防止int越界
注意:爆int不会报错,只会使得结果出错