我又来反刍了。。。
图的概念,图的特殊类型,图的最短路算法
特殊图的类型
1)树
无环 无向 连通图
2)森林
无环 无向
3)有向图的树
无环 连通
a)外向树
所有的边由浅向深(由上向下指)
b)内向树
所有的边由深向浅指
c)其他普通的有向图的树
注:有向图的内外由所选的树根决定
4)章鱼图(基环树)
只有一个环的图,n个点的图总共有n条边
章鱼变树:删掉一条环边
树变章鱼:在树上任意连一边
章鱼图的考点:DP相关,树形DP的拓展(有点难)
例题:没有上司的舞会(一个没有环的树形DP)(n <= 10^5)
解:f[i] [0/1]表示以i为根的子树里,i这个点没选/选了时,最多能选多少个点
f[i] [0] = (sum {max(f[son] [0], f[son] [1])})
f[i] [1] = (sum {f[son] [0] })
改成章鱼图,章鱼图的DP思路:
如果环上有一些点,叫做p_i
Step1:对每个点延伸出去的树做树形DP
Step2:环形DP,做环形DP之前必须要预先求出树形DP
g[i] [0/1] [0/1] 表示选到第i个点,第i个点选没选,起点选没选
g[i] [0] [0/1] = max(g[i - 1] [0], g[i - 1] [1]) + f[p_i] [0]
g[i] [1] [0/1] = g[i - 1] [0] + f[p_i] [1]
题外话:
###题目不会告诉我们环在哪——dfs找环
在dfs的时候,从根向子树dfs,访问到曾经访问过的点时,形成环
###递归
Windows:5w层
Linux:50w层
附赠2题:https://www.luogu.com.cn/problem/P2607
noi2012day2t1
5)仙人掌图
a)边仙人掌图
一棵树,把树上的每一个点都变成环,环与环之间是由边连接的
b)点仙人掌
一棵树,把树上的每一个点都变成环,环与环之间是由点连接的???
c)仙人掌的题考点:树形DP和环形DP交替进行,无限套娃
( 啊啊啊07年-13年的缺德出题人就喜欢瞎出
6)DAG(Derected Acyclic Graph)有向无环图
99%是用于DP
7)二分图
前提是无向图,图分为左右两部分,所有的边都是连在左右图之间的,左图或右图内部没有边,二分图不用保证连通,边集是空集的图一定是二分图
等价命题:有奇环(环上的点数是奇数)的图不是二分图
无奇环的图一定是二分图
判断是否是二分图
用dfs或bfs的做法,用染色的思想,一个点有边相连的点和自己染色不同
结束情况1:所有点完成染色,没有奇环,是二分图
结束情况2:一个点周围有一个和自己同色的点,有奇环,不是二分图
二分图
a)树是二分图,按深度分类,奇数深度的点在左,偶数深度的点在右
b)方格图是二分图,因为方格图可以完成染色,比如说国际象棋和国际跳棋的棋盘
QAQ:二分图是能考的图里面思维难度很高的一种
存图
1)邻接矩阵,预设边数是n^2,但是实际的边数往往是n级别的(n约等于m),而且不好处理重边。但是一些题只能用邻接矩阵
2)边表,或者叫做链式前向星
再次写代码:
struct edge{
int e, next;//e表示这条边指向e,next是下一条边的编号
}sd[maxm];
int en, first[maxn];
void add_edge(int s, int e){
en++;
ed[en].next = first[s];
firsd[s] = en;
ed[en].e = e;
}
算法-最短路
//md自己的最短路好像从初二就学了,然而一个题都没写过
单源最短路
一个点到所有点的最短路
多源最短路
多个点到所有点的最短路,显然可以通过多次单源最短路解决,不过有点慢
最短路中的三角不等式
存在边i-k,i-j,j-k
dist[i] [k] <= dist[i] [j] + dist[j] [k]
松弛操作
算法来了
1)Floyd 唯一一个可以计算多源最短路的算法,具有不可替代性
本质是三维DP,时间复杂度和空间复杂度都是O(n^3)
f[i] [j] [k] 表示 满足(从j出发,走到k,中间经过的所有点的编号都小于等于i)的最短路长度
f[i] [j] [k] = min(f[i - 1] [j] [k], f[i - 1] [j] [i], f[i - 1] [i] [k])
最后x-y的最短路长度就是f[n] [x] [y]
滚动数组压维:
注意到求出f[i] [j] [k]之后,f[i -1] [j] [k] 就没用了,所以可以滚动
2)Dijkstra 前提条件是无负边权
两个桶,左桶里是没有找到最短路的点,右边桶里时找到最短路的点
松弛:从左边找一个点1,此时1号点在右桶,用1号点来松弛自己所相连的x点,dist[x] = min(dist[x], dist[1] + len[1] [x])
代码:
void dijkstra(){
//源点为s
memset(dist, 0x3f, sizeof(dist));
dist[s] = 0;
for(int i=1;i<=n;i++){
p = -1;
for(int j=1;j<=n;j++){
if(!right[j])//right表示有没有在右边,!是不在右边
{
if(p == -1 || dist[p] > dist[j]) p=j;//p是左边桶里到s距离最小的点
}
}
right[p] = true;//放到右桶
for(int j = first[p]; j!=0;j=ed[j].next){//用所有p连出的边松弛
dist[ed[j].e] = min(dist[ed[j].e], dist[p] + ed[j].d); //d表示距离
}
}
}
复杂度:O(n^2+m)
Dijkstra+heap
优化:注意到Dijkstra第二个for的目的是找到最小值,然后删了它,注意到第三个for的目的是修改最小值
为了优化复杂度,并且达到修改,删除,询问的目的,我们可以使用堆或线段树
(md我现在还不会写STL的堆)
代码
//堆
struct point{
int p, d;//源点到p的最短路距离是d
point(){} //初始函数。当新声明了一个结构体,会自动执行无参数的初始函数
point(int a, int b){p = a, d = b};//构造函数
};
bool operator<(const point &a, const point &b){//大根堆
return a.d > b.d;//大于号和大根堆配合食用,成为了小于号
}
void dijkstra(){
//源点为s
memset(dist, 0x3f, sizeof(dist));
dist[s] = 0;
for(int i = 1; i<=n;i++){
heap.push(point(i,dist[i]));//构造函数
}
for(int i=1;i<=n;i++){
while(right[heap.top().p])
heap.pop();//由于我们39行是只塞不拿,为了避免一个点从左桶里取出多次,用right来判断
point now = heap.top();
heap.pop();
int p=now.p,d=now.d;
right[p] = true;//放到右桶
for(int j = first[p]; j!=0;j=ed[j].next){//用所有p连出的边松弛
int e=ed[j].e;
int d=dist[p] + ed[j].d;
if(dist[e] > d){
dist[e] = d;
heap.push(point(e,d));//直接塞进更短的最短路
}
// dist[ed[j].e] = min(dist[ed[j].e], dist[p] + ed[j].d); //d表示距离
}
}
}
复杂度:O[(n+m) log (n + m) ]
看起来堆的复杂度是logn,外层循环进行了n次,看起来复杂度是nlogn
但是注意到我们39行在往堆里塞边,最坏会塞m条边,堆的大小就变成了n+m
所以复杂度是O[( n+m)log (n+m)]
再优,再优我就不会了
手写堆:(n+m)logn
斐波那切数列堆:nlogn+m
Bellman_Floyd
从i号点到j号点的最短路经过的边数不超过n-1
代码:
int main()
{
for(int i = 1; i <= m; i++){
cin >> s[i] >>e[i] >> d[i];//起点,终点,距离
}
memset(dist,0x3f,sizeof(dist));
dist[1]=0;
for(int i=1;i<n;i++){//n-1次
for(int j=1;j<=m;j++){//每次拿所有的边更新一次
dist[e[j]] = min(dist[e[j]], dist[s[j]] + d[j]);
}
}
return 0;
}
复杂度:O(nm)
虽然很好写,但是很慢
Bellman-Floyd再优化,变成SPFA
代码
void spfa(int s){
memset(dist, 0x3f, sizeof(dist));
dist[s]=0;
queue<int>q;
q.push(s);
while(q.size()){//q是待更新队列,队列里的东西是可以更新点的
int now=q.front();
q.pop();
inque[now] = false; //判断一个点是否在队
for(int i=first[now];i!=0;i=ed[i].next){
int e=ed[i].e;
int d=dist[now] + ed[i].d;
if(dist[e] > d){
dist[e]=d;//e的最短路变短了,说明dist[e]可以去更新其他点了
if(!inque[e]) inque[e]=true,q.push(e);//塞进队列,注:一个点可以多次进队
}
}
}
}
最差复杂度:O(nm)
随机复杂度:O(n)
多源最短路——floyd
单源最短路——没有负边权——dijkstra+heap
单源最短路——有负边权——SPFA
######################zhx曰:
初学OI的时候,常常一个题写一周,觉得这也是件好事,毕竟写出来了。写代码是实验学科,平时一定要多写多练