图论baozi
1.堆优化的dij
- 使用优先队列https://blog.csdn.net/qq_36561697/article/details/82194569
-
priority_queue<Type, Container, Functional>
Type为数据类型, Container为保存数据的容器,Functional为元素比较方式。
如果不写后两个参数,那么容器默认用的是vector,比较方式默认用operator<,也就是优先队列是大顶堆,队头元素最大。
- 如果要输出小数据的话:
- priority_queue<int, vector<int>, greater<int> > p;
- 代码如下
-
View Code#include <cstdio> #include <algorithm> #include <cstring> #include <queue> using namespace std; const int maxn=1e5+10; typedef pair<int,int> PII; int head[maxn],nex[maxn],ver[maxn],wei[maxn],tot; int n,m; priority_queue<PII,vector<PII>,greater<PII>> q;//这里要设置成PII int dis[1000]; bool st[1000]; inline void add(int x,int y,int w) { ver[++tot]=y; wei[tot]=w; nex[tot]=head[x]; head[x]=tot; } inline void dij() { q.push({0,1}); memset(dis,0x3f,sizeof(dis)); dis[1]=0; while(q.size()) { auto t=q.top(); q.pop(); int x=t.second,d=t.first; if(st[x]) continue; st[x]=true; for(int i=head[x];i;i=nex[i]) { int y=ver[i]; if(dis[y]>d+wei[i])//如果有的话就更新 { dis[y]=d+wei[i]; q.push({dis[y],y}); } } } } int main() { scanf("%d%d",&n,&m); for(int i=1;i<=m;i++) { int a,b,c; scanf("%d%d%d",&a,&b,&c); add(a,b,c); } dij(); if(dis[n]==0x3f3f3f3f) printf("-1");//四组3f else printf("%d",dis[n]); return 0; }
请注意,dij的适用范围是非负权回路(所以有0边是可以实现的)
- 注意进入的是dis[y】而非y
2.spfa:处理带有负权边和环的最短路算法
- 注意不要头铁直接下read
- 有可能会炸
-
View Code#include <stdio.h> #include <algorithm> #include <cstring> #include <queue> #include <cctype> using namespace std; const int maxn=1e5+10; int head[maxn],nex[maxn],wei[maxn],ver[maxn],tot; int n,m; bool st[maxn]; int dis[maxn]; queue<int>q; inline int read() { int x=0,b=1;char c=getchar(); while(!isdigit(c)) b=c=='-'?-1:1,c=getchar(); while(isdigit(c)) x=x*10+c-'0',c=getchar(); return x*b; } inline void add(int x,int y,int w) { ver[++tot]=y; wei[tot]=w; nex[tot]=head[x]; head[x]=tot; } inline void spfa() { q.push(1); memset(dis,0x3f,sizeof(dis)); dis[1]=0; st[1]=true; while(q.size()) { int x=q.front(); q.pop(); st[x]=false; for(int i=head[x];i;i=nex[i]) { int y=ver[i]; if(dis[y]>dis[x]+wei[i]) { dis[y]=dis[x]+wei[i]; if(!st[y]) { st[y]=true; q.push(y); } } } } } int main() { n=read(),m=read(); for(int i=1;i<=m;i++) { int a,b,c; a=read(),b=read(),c=read(); add(a,b,c); } spfa(); // for(int i=1;i<=n;i++) printf("%d ",dis[i]); if(dis[n]==0x3f3f3f3f) printf("impossible"); else printf("%d",dis[n]); return 0; }
2.1适用spfa算法判断负环:
- 大体原理相同,不需要初始化dis数组
- 根据抽屉原理,如果一个到一个点的最短路长度为n,至少经过n+1个点,矛盾
- 维护cnt数组
- 负环可能和1不连通
- 所以初始把所有点都加进去就可
- 代码如下
#include <stdio.h> #include <algorithm> #include <cstring> #include <queue> #include <cctype> using namespace std; const int maxn=1e5+10; int head[maxn],nex[maxn],wei[maxn],ver[maxn],tot; int n,m; bool st[maxn]; int dis[maxn],cnt[maxn]; queue<int>q; inline int read() { int x=0,b=1;char c=getchar(); while(!isdigit(c)) b=c=='-'?-1:1,c=getchar(); while(isdigit(c)) x=x*10+c-'0',c=getchar(); return x*b; } inline void add(int x,int y,int w) { ver[++tot]=y; wei[tot]=w; nex[tot]=head[x]; head[x]=tot; } inline bool spfa() { for(int i=1;i<=n;i++) { st[i]=true; q.push(i); } while(q.size()) { int x=q.front(); q.pop(); st[x]=false; for(int i=head[x];i;i=nex[i]) { int y=ver[i]; if(dis[y]>dis[x]+wei[i]) { dis[y]=dis[x]+wei[i]; cnt[y]=cnt[x]+1; if(cnt[y]==n) return true; if(!st[y]) { st[y]=true; q.push(y); } } } } return false; } int main() { n=read(),m=read(); for(int i=1;i<=m;i++) { int a,b,c; a=read(),b=read(),c=read(); add(a,b,c); } if(spfa()) printf("Yes"); else printf("No"); // for(int i=1;i<=n;i++) printf("%d ",dis[i]); return 0; }
3.最近公共祖先:
3.1倍增求lca(预处理O(nlogn)查询O(logn))
3.2离线求lca(感觉没有什么用)trajan求lcaO(n+m)
- 核心思路:将已经搜索并且回溯过的点使用并查集合并挂在点上,并且将查询离线就可
- 关键:在该节点回溯完成后合并
- 【lca】:注意在树上两点间的距离就是dis[x]+dis[y]-2*dis[lca](dis存储节点到根节点的距离)
4.拓扑排序:
- 一个图可以拓扑排序(有向无环图);
- 本质上相当于bfs算法:
- 统计入度和出度;
- 首先将所有入度为0的点来入队
-
View Code#include <stdio.h> #include <algorithm> #include <cstring> #include <iostream> using namespace std; const int maxn=2000; int head[maxn],nex[maxn],ver[maxn],tot; int q[maxn]; int d[maxn]; int n; inline void add(int x,int y) { ver[++tot]=y; nex[tot]=head[x]; head[x]=tot; } inline void topsort() { int hh=0,tt=-1; for(int i=1;i<=n;i++) if(d[i]==0) q[++tt]=i; while(hh<=tt) { int x=q[hh++]; for(int i=head[x];i;i=nex[i]) { int y=ver[i]; if(--d[y]==0) q[++tt]=y; } } } int main() { scanf("%d",&n); for(int i=1;i<=n;i++) { int son; while(scanf("%d",&son),son) { add(i,son); d[son]++; } } topsort(); for(int i=0;i<n;i++)printf("%d ",q[i]); return 0; }
注意,q队列实际上也就是拓扑序的队列:
- 每次选择队列最前端的出队,删边
- 如果有入度为0的点就入队就可以了
4.1 差分约束问题:(求最长路)【边权无限制】spfaO(km)
如果说所以边权都是非负的:强联通分量求解(有向图trajan)
如果说所有边权都是1的话,就可以使用拓扑排序来求解
5.树的直径:
- 证明略:先证明任取一点a,求最远点u一定是树的直径的某一个直径的一个端点【经典做法】
- 经典做法:两次dfs求最远点(限制:每个点的边权都是1)
- 如果说边权没有限制的话,我们就要使用树形dp的方法来求
- 【总体,集合,属性,状态,转移,最后一步,优化】
- 注意树边的特性有两种:一条路走到黑或者挂上去的边
- 注意,在dfs的过程当中不能用dist而是要用d来更新(因为最大边可能更新两次替代了次大的边)
- 代码如下:
-
View Code#include <stdio.h> #include <algorithm> #include <cstring> using namespace std; const int maxn=20020; int head[maxn],nex[maxn],ver[maxn],wei[maxn],tot; int n,ans; inline int dfs(int u,int father) { int dist=0; int d1=0,d2=0; for(int i=head[u];i;i=nex[i]) { int y=ver[i]; if(y==father) continue; int d=dfs(y,u)+wei[i]; dist=max(dist,d); if(d>=d1) d2=d1,d1=d;//这里不能使用dist而是要用dis来进行更新 else if(d>d2) d2=d; } ans=max(ans,d1+d2); return dist; } void add(int x,int y,int w) { ver[++tot]=y; wei[tot]=w; nex[tot]=head[x]; head[x]=tot; } int main() { scanf("%d",&n); for(int i=1;i<=n-1;i++) { int a,b,c; scanf("%d%d%d",&a,&b,&c); add(a,b,c);add(b,a,c); } dfs(1,-1); printf("%d",ans); return 0; }
6.割点和桥:
- 极大的不包含桥或者点的联通量(极大就是不被包含)
- 点至少被包含在两个连通分量里面
浙公网安备 33010602011771号