【笔记】清北学堂图论专题day1-1基础图论

我又来反刍了。。。
图的概念,图的特殊类型,图的最短路算法

特殊图的类型

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的时候,常常一个题写一周,觉得这也是件好事,毕竟写出来了。写代码是实验学科,平时一定要多写多练

posted @ 2020-04-11 11:59  _Buffett  阅读(269)  评论(1编辑  收藏  举报