zoukankan      html  css  js  c++  java
  • 图论笔记

    图论

    最小生成树

    (N)个城市,(M)条可修的公路,每条公路有一个修的成本(w_i),要使(N)个城市连通,所需要的最低成本?

    最少需要(N-1)条边,构成一棵树。

    Kruskal算法证明

    对图的顶点数(n)做归纳,证明(Kruskal)算法对任意(n)阶图都适用

    归纳基础

    (n=1),显然能找到最小生成树

    归纳过程

    例题

    P1550 [USACO08OCT]打井Watering Hole

    思路就是建立一个超级源点往每个点建一条边权为(w_i)的边,然后跑最小生成树

    怎么证明合数都可以分成素数的乘积

    就不写了,我太菜了

    在最大生成树上求两点路径中边权的最小值

    int f[N][20]; //i的第2^j祖先
    int mn[N][20]; //i往上跳2^j祖先所经过的边的最小值 
    int query(int u,int v) {
    	int ans=inf;
    	if(dep[u]>dep[v]) swap(u,v);
    	for(int i=0,k=dep[v]-dep[u];i<=LG[k];++i)
    		if(k>>i&1) {
    			ans=min(ans,mn[v][i]);
    			v=f[v][i];
    		}
    	if(u==v) return ans;
    	for(int i=LG[dep[u]];i>=0;--i)
    		if(f[u][i]!=f[v][i]) {
    			ans=min(ans,min(mn[u][i],mn[v][i]));
    			u=f[u][i],v=f[v][i];
    		}
    	ans=min(ans,min(mn[u][0],mn[v][0]));
    	return ans;
    }
    
    

    kruskal重构树

    orz lgj 学长,竟然是学长的博客

    最短路

    SPFA判负环

    queue<int> q;
    bool inq[N];
    int dis[N], len[N];
    bool spfa() { //返回值为true表示有负环 否则没有负环 
    	fill(dis+1,dis+n+1,inf);
    	dis[1]=0,len[1]=1;
    	q.push(1);
    	inq[1]=true;
    	while(!q.empty()) {
    		int u=q.front() ; q.pop();
    		inq[u]=false;
    		for(int i=0;i<e[u].size();++i) {
    			int v=e[u][i];
    			int w=W[u][i];
    			if(dis[u]+w<dis[v]) {
    				dis[v]=dis[u]+w;
    				len[v]=len[u]+1;
    				if(len[v]>=n) return true;
    				if(!inq[v]) q.push(v),inq[v]=true;
    			}
    		}
    	}
    	return false;
    }
    
    

    建图技巧

    通过添加虚拟点等手段将问题转化

    从而达到减少边数等目的。

    骚操作,长见识了

    拓扑排序

    在一个(DAG)有向无环图) 中,我们将图中的顶点以线性方式进行排序,使得对于任何的顶点 (u)(v) 的有向边 ((u,v)) , 都可以有 (u)(v) 的前面。

    //拓扑排序 O(n^2)
    int ind[N]; //每个点的入度
    int seq[N],cnt; //求出的拓扑序
    bool vis[N];
    bool toposort() { //有环返回false 
    	while(true) {
    		if(cnt==n) return true;
    		int k=0;
    		for(int i=1;i<=n;++i)
    			if(!vis[i]&&ind[i]==0) {
    				k=i; break;
    			}
    		if(k==0) return false;
    		seq[++cnt]=k; vis[k]=true;
    		for(int i=0;i<e[k].size();++i) {
    			int u=e[k][i];
    			--ind[u];
    		}
    	}
    }
    

    queue优化

    (O(n+m))

    //拓扑排序 O(n+m)
    queue<int> q; //q中维护入度为0的点 
    int ind[N],seq[N],cnt;
    bool toposort() { //有环返回false
    	for(int i=1;i<=n;++i)
    		if(!ind[i]) q.push(i);
    	while(!q.empty()) {
    		int u=q.front(); q.pop();
    		seq[++cnt]=u;
    		for(int i=0;i<e[u].size();++i) {
    			int v=e[u][i];
    			--ind[v];
    			if(ind[v]==0) q.push(v);
    		}
    	}
    	return cnt==n;
    }
    

    tarjian

    推荐洛谷日报
    Tarjan算法运行过程
    1.按照深度优先遍历的方式遍历这张图。

    2.遍历当前节点所出的所有边。在遍历过程中:

    ( 1 ) 如果当前边的终点还没有访问过,访问。回溯回来之后比较当前节点的low值和终点的low值。将较小的变为当前节点的low值。(因为遍历到终点时有可能触发了2)
    
    ( 2 ) 如果已经访问过,那我们一定走到了一个之前已经走过的点(终点的时间戳一定比当前的小)
    

    3.则比较当前节点的low值和终点的dfn值。将较小的变为当前节点的low值

    4,在回溯过程中,对于任意节点u用其出边的终点v的low值来更新节点u的low值。因为节点v能够回溯到的已经在栈中的节点,节点u也一定能够回溯到。因为存在从u到v的直接路径,所以v能够到的节点u也一定能够到。

    当一个节点的dfn值和low值相等时,这个节点是一个强联通分量的“根”。压栈,输出。

    int dfn[N],low[N],dfsclock;
    int s[N],top;
    int cnt; //当前联通块的编号
    int bl[N]; //bl[i]表示i所在的强联通分量编号 
    vector<int> scc[N]; //scc[i]中存储编号为i强联通分量中的所有点
    void dfs(int u) {
    	low[u]=dfn[u]=++dfsclock;
    	s[++top]=u;
    	for(int i=0;i<e[u].size();++i) {
    		int v=e[u][i];
    		if(dfn[v]) low[u]=min(low[u],dfn[v]);
    		else {
    			dfs(v);
    			low[u]=min(low[u],low[v]);
    		}
    	}
    	if(low[u]==dfn[u]) {
    		++cnt;
    		int v=s[top];
    		while(v!=u) {
    			bl[v]=cnt;
    			scc[cnt].push_back(v);
    			v=s[--top];
    		}
    		bl[u]=cnt;
    		scc[cnt].push_back(u);
    	}
    }
    

    缩点

    一个强连通分量里的点如果看成是一个大点的话,图就可以变成有向无环图((DAG)),然后递推什么的就方便了

    差分约束

    以后再写吧,感觉就知道是吧不等式组建了个图...我太弱了

    环套树/基环树

    我们知道,树是最简单的连通无向图。

    树中任意两点之间有且只有一条路径

    环套树/基环树:树中加一条边的图。

    设加的一条边为((u,v)),那么由之前(u,v)间有一条路径,加了这条边之后这条路径和这条边构成一个环。

    这也是形成的唯一一个环

    我们可以把环放在图中心,这样看起来环上每个点都是一个树根。
    找到环的位置很重要

    方法类似(noip2016 信息传递)

    基环树找环

    找环的步骤:

    1.随意找一个点开始当成树进行(dfs),并记录每个结点访问的时间戳(dfn)

    2.(dfs)的过程中一定会有一个点往(dfn)比自己小的点连了边,那么这条边可以看成加上的那条。记录下这条边((u,v))

    3.暴力让(u)(v)往上爬到根,记录他们分别经过的点。

    4.深度最大的他们都经过的点为他们的(lca)(u->lca)之间的所有点(+v->lca)之间的所有点即构成环。

    //求基环树中的环 方法一
    int fa[N]; //f[i]为i在搜索树中的父亲结点 
    bool in[N],vis[N]; //表示i是否在环中
    int cir[N],cnt;
    int x,y;
    void dfs(int u,int f) { //f为u的父亲结点 
    	fa[u]=f; vis[u]=1;
    	for(int i=0 ;i<e[u].size();++i) {
    		int v=e[u][i];
    		if(v==f) continue;
    		if(vis[v]) x=u,y=v;
    		else dfs(v,u);
    	}
    }
    void solve() { //x到y一定是返祖边 
    	dfs(1,0);
    	while(x!=y) {
    		cir[++cnt]=x;
    		x=fa[x];
    	}
    	cir[++cnt]=y;
    }
    
    //方法二
    queue<int>q;
    int deg[N],n;
    void solve()
    {
        for(int i=1;i<=n;++i) in[i]=1;
        for(int i=1;i<=n;++i)
            if(deg[i]==1) q.push(i);
        while(!q.empty())
        {
            int u=q.front();
            q.pop();
            int[u]=0;
            for(int i=0;i<e[u].size();++i)
            {
                int v=e[u][i];
                deg[v]--;
                if(deg[v]==0) q.push(v);
            }
    	}
        int u;//u作为环的起点
        for(u=1;u<=n;++u)
       	if(in[u]) break;
        int v=u,las=0;//把u放环里
        do{
            cir[++cnt]=v;
            int k;
            for(i=0;i<e[v].size();++i)
            {
                k=e[v][i];
                if(in[k] && k!=las) break;
    		}
            la
            v=k;
        }while(v!=u)
    }
    

    欧拉图

    通过图中所有边一次且仅一次行遍所有顶点的通路称为欧拉通路。

    通过图中所有边一次且仅一次行遍所有顶点的回路称为欧拉回路。

    具有欧拉回路的图称为欧拉图。

    具有欧拉通路的图称为半欧拉图。

    有向图的时候可以类似地定义。

    (G)是欧拉图当且仅当(G)是连通的且没有奇度顶点。

    $ G(是半欧拉图当且仅当)G$中恰有两个奇度顶点。

    证:

    连通且没有奇度顶点=>一定存在一个环

    那么我们把这个环上的边删掉,剩下的图仍满足连通且没有奇数顶点

    由连通性这些环可以是有公共点的,可以拼起来

    圈套圈算法

    (dfs)搜索,不能再往下走(不能重复使用一条边,但可以重复经过一个点)便回溯,回溯时记录路径,回溯时不清除对边的标记,最后求出来的路径就是欧拉回路。

    给边编号,用(vis)数组记录每个边是否访问过

    //欧拉回路 圈套圈算法
    struct edge
    {
        int v,nex,id;
    };
    bool vis[N];
    int s[N],top;
    int seq[N],cnt;
    void dfs(int u)
    {
        s[++top]=u;
        for(int i=head[u];i;i=e[i].nex)
        {
            if(vis[e[i].id]) continue;
            vis[e[i].id]=true;
            dfs(e[i].v);
    	}
        --top;
        seq[++top]=e[i].v;
    }
    
  • 相关阅读:
    办公开发环境(外接显示屏,wifi热点)
    awk, sed, xargs, bash
    regular expression, grep (python, linux)
    Linux环境常用命令
    Approximate timing for various operations on a typical PC
    numpy初用
    shell代码模板
    virtualenv(for python)
    《Oracle RAC性能优化》
    【Oracle 集群】ORACLE DATABASE 11G RAC 知识图文详细教程之缓存融合技术和主要后台进程(转)
  • 原文地址:https://www.cnblogs.com/pyyyyyy/p/11329739.html
Copyright © 2011-2022 走看看