图论

拓扑排序

  • 一般不会考裸题,会判环或配合\(dp\)

  • \(bfs\)

bool topo() {
	queue <int> q; int cnt = 0;
	for(int i = 1;i <= n; ++i) if(!ind[i]) q.push(i);
	while(!q.empty()) {
		int u = q.front(); q.pop(); ++cnt;
		for(int i = head[u]; i; i = e[i].nxt) {
			int v = G[u][i];
			--ind[v];
			if(!ind[v]) q.push(v);
		}
	}
	return cnt == n;// == topo成功, != 则失败
}

最小生成树

  • 最小生成树有许多变式,此处先只讨论最简单的

\(Kruskal\)(贪边)

  • 适用于稀疏图
struct edge {
    int u,v,w;
    bool operator < (const edge &sed) const {
        return w < sed.w;
    }
}e[M];

int find(int x) {
    return fa[x] == x ? x : fa[x] = find(fa[x]);
}

int main() {
    int i; in(n); in(m);
    for(i = 1;i <= m; ++i) {
        in(e[i].u); in(e[i].v); in(e[i].w);
    }
    sort(e+1,e+m+1);
    for(i = 1;i <= n; ++i) fa[i] = i;
    int cnt = 0;
    for(i = 1;i <= m; ++i) {
        int x = find(e[i].u),y = find(e[i].v);
        if(x == y) continue;
        ++cnt; fa[x] = y; ans += e[i].w;
        if(cnt == n-1) break;
    }
    if(cnt == n-1) out(ans);
    else printf("orz");
    return 0;
}

\(Prim\)(贪点)

  • \(n^2\)算法适用于稠密图(特别是类似二维平面的所有点之间都有边的完全图)
for(k = 1;k <= n; ++k) {//枚举轮数(n个点,每轮标记1个,所以有n轮)
    for(u = 0,i = 1;i <= n; ++i) if(!vis[i] && dis[i] < dis[u]) u = i;
	//找到未被标记的dis值最小的点
    for(v = 1;v <= n; ++v) if(!vis[v] && maps[u][v] < dis[v]) dis[v] = maps[pr[v] = u][v];
	//更新 
}
  • 思想和算法框架与求最短路的\(Dijkstra\)几乎一致
struct node {
    int pos,dis;
    node(int pos = 0,int dis = 0):pos(pos),dis(dis){};
    bool operator < (const node &sed) const {
        return dis > sed.dis;//debug 优先队列默认是大根堆
    }
};

priority_queue <node> q;

void prim() {//贪点 
    memset(dis,0x3f,sizeof(dis));
    q.push(node(1,0)); dis[1] = 0; int cnt = 0;//debug 此处cnt为已加入树中的点数(不同于kruskal的贪边) 
    while(!q.empty()) {
        node _u = q.top(); q.pop();
        int u = _u.pos;
        if(vis[u]) continue; vis[u] = 1;
        ++cnt; ans += _u.dis;
        if(cnt == n) break;//注意 
        for(int i = head[u]; i;i = e[i].nxt) {
            int v = e[i].v;
            if(dis[v] > e[i].w) {
                dis[v] = e[i].w; q.push(node(v,dis[v]));
            }
        }
    }
    if(cnt == n) out(ans);
    else printf("orz");
}//prim 的写法与dijkstra如出一辙 主要区别在于dijkstra的dis代表到源点的最短距离,而prim的dis代表未加入树集的点到树集(连通块)的最小距离 
//本质上都是贪心

最短路

  • 单源最短路

\(SPFA\)(\(SLF\)优化)

void spfa() {
    memset(dis,0x3f,sizeof(dis));
    q.push_back(s); dis[s] = 0; inque[s] = 1;
    while(!q.empty()) {
        int u = q.front(); q.pop_front(); inque[u] = 0;//易错未写 inque[u] = 0 
        for(int i = head[u]; i;i = e[i].nxt) {
            int v = e[i].v;
            if(dis[v] > dis[u]+e[i].w) {
                dis[v] = dis[u]+e[i].w;
                if(!inque[v]) {
                    if(!q.empty() && dis[v] < dis[q.front()]) q.push_front(v);//注意 易错未写 !q.empty()
                    else q.push_back(v);
                    inque[v] = 1;
                }
            }
        }
    }
}

\(Dijkstra\)(堆优化)

struct node {
    int pos; ll dis;
    node(int pos = 0,ll dis = 0):pos(pos),dis(dis){};
    bool operator < (const node &sed) const {
        return dis > sed.dis;//debug 又写反了 
    }
};

priority_queue <node> q;

void dijkstra() {
    memset(dis,0x7f,sizeof(dis));
    q.push(node(s,0)); dis[s] = 0; //vis[s] = 1;
    while(!q.empty()) {
        node _u = q.top(); q.pop();
        int u = _u.pos;
        if(vis[u]) continue; vis[u] = 1;
        for(int i = head[u]; i;i = e[i].nxt) {
            int v = e[i].v; 
            if(dis[v] > dis[u]+e[i].w) {
                dis[v] = dis[u]+e[i].w;
                //if(vis[v]) continue;
                q.push(node(v,dis[v]));
                //vis[v] = 1;
            }
        }
    }
}

\(Floyd\)

多源最短路算法,时间复杂度 \(O(n^3)\)

  • 基础应用

\(floyd\)的本质是\(dp\),设\(f[k,i,j]\)表示只经过前\(k\)个节点从\(i\)\(j\)的最短路长度

\[f[k,i,j] = min(f[k-1,i,j],f[k-1,i,k]+f[k-1,k,j]) \]

即对于每个点对\((i,j)\),每个点只有两种决策——被经过或不被经过

\(第一维可省略\)

即有

\[f[i,j] = min(f[i,j],f[i,k]+f[k,j]) \]

  • 传递闭包

\[f[i,j] \;|= f[i,k]\&f[k,j] \]

  • 求最小环
  1. \(dijkstra\)

    枚举每条边,求删去这条边后此边所连两点的最短路,即做\(m\)\(dijkstra\)

  2. \(floyd\)

    设已知\(k,i,j\),其中\(k\)\(i,j\)邻接,此时求最小环只需求得\(i,j\)不经过\(k\)的最短路(答案就是这个再加上两个边权).

    考虑\(floyd\)的过程,枚举\(k\)的时候\(f[i,j]\)表示是只考虑前\(k-1\)个点的最短路,的确没有经过\(k\),但问题是最小环可能还包含大于\(k\)的点

    考虑增加限制,每次求\(k\)为最大点的最小环,最小环一定有一个最大点\(k'\),\(k'\)一定会被枚举到,所以这样也一定可以求得答案

    我们可以在每次转移前顺便求得\(k\)为最大点的最小环

  • 代码
for(int k = 1;k <= n; ++k) {
	for(int i = 1;i < k; ++i) {
		for(int j = i+1;j < k; ++j) {//对称的点对(i,j)和(j,i)是等价的(无向图),所以不妨设i<j
			ans = min(ans,f[i][j]+a[j][k]+a[k][i]);
		}
	}
	for(int i = 1;i <= n; ++i) {
		for(int j = 1;j <= n; ++j) {
			f[i][j] = min(f[i][j],f[i][k]+f[k][j]);
		}
	}
}
  • 输出方案
vector <int> path;

void get_path(int x,int y) {
	if(!g[x][y]) return;
	get_path(x,g[x][y]);
	path.push_back(g[x][y]);
	get_path(g[x][y],y);
}

memset(a,0x3f,sizeof(a));
memset(dis,0x3f,sizeof(dis));
for(i = 1;i <= n; ++i) a[i][i] = 0;
for(i = 1;i <= m; ++i) {
	in(u); in(v); in(w);
	dis[u][v] = dis[v][u] = a[u][v] = a[v][u] = min(a[u][v],w);
}
for(k = 1;k <= n; ++k) {
  	for(i = 1;i < k; ++i) {
		for(j = i+1;j < k; ++j) {
			ll tmp = dis[i][j]+a[j][k]+a[k][i];
			if(ans > tmp) {
				ans = tmp;
				path.clear();
				path.push_back(i); get_path(i,j);
				path.push_back(j); path.push_back(k);
			}
		}
	}
	for(i = 1;i <= n; ++i) {
		for(j = 1;j <= n; ++j) {
			ll tmp = dis[i][k]+dis[k][j];
			if(dis[i][j] > tmp) {
				dis[i][j] = tmp;
				g[i][j] = k;
			}
		}
	}
}
if(ans == inf) printf("No solution.");
else {
	for(i = 0;i < path.size(); ++i) {
		out(path[i]); putchar(' ');
	}
}
posted @ 2019-09-18 19:51  陈星卿  阅读(148)  评论(0编辑  收藏  举报