图论
拓扑排序
-
一般不会考裸题,会判环或配合\(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]
\]
- 求最小环
-
\(dijkstra\)
枚举每条边,求删去这条边后此边所连两点的最短路,即做\(m\)次\(dijkstra\)
-
\(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(' ');
}
}