zoukankan      html  css  js  c++  java
  • 【笔记】清北学堂图论专题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的时候,常常一个题写一周,觉得这也是件好事,毕竟写出来了。写代码是实验学科,平时一定要多写多练

  • 相关阅读:
    eclipse中jdk源码调试步骤
    [POJ2777] Count Color
    [HNOI2004] L语言
    [USACO08DEC] 秘密消息Secret Message
    The XOR Largest Pair [Trie]
    前缀统计 [Trie]
    于是他错误的点名开始了 [Trie]
    Palindrome [Manecher]
    兔子与兔子 [Hash]
    [CF985F] Isomorphic Strings
  • 原文地址:https://www.cnblogs.com/ZhengkunJia/p/12678636.html
Copyright © 2011-2022 走看看