图:点和边组成。
V为顶点集,E为边集,图记为G(V,E),连接两点u和v的边用e=(u,v)表示。
分为有向图和无向图。边上有权值的是带权图。
连通图:任意两点间均可达。度数是顶点连的边数。
树:没有圈的连通图。边数=点数-1。
有向图:度数分为入度和出度。
没有圈的有向图称为DAG(Direct Acyclic Graph)。
对DAG给顶点标记顺序,就是拓扑序。求拓扑序的算法是拓扑排序。
邻接矩阵:|V|*|V|二维数组来表示图。g[i] [j]表示顶点 i 和 j 的关系
在权值情况下,通常没连的边设为INF。
邻接表:空间占的少,取值慢一点。
一种STL写法:
vector G[VMAX];
for(int i = 0; i < E; i++){
int s,t;
scanf("%d%d",&s,&t);
G[s].push_back(t);
}
另一种头接法(更少空间、更快插入、更慢查找):
struct Edge{
int to,nex;
}e[EMAX];
int etot = 0;
int head[NMAX]; //每个点的第一个边
void init(){
etot=0;
memset(head,-1,sizeof(head));
}
void addedge(int u,int v){
e[etot].to = v;
e[etot].next = head[u];
head[u] = etot++;
}
//遍历like:
for(int i=head[now];i!=-1;i=head[i]){
Edge te = e[i];
}
例题1:二分图判定
给定一个具有n个顶点的图,用两种颜色给图上每个顶点染色,相邻顶点颜色不同。无重复的边和自环。
问能否染色?
1<=n<=1000
思路:dfs一遍搞定。±1分别代表黑白色,若未染过就是0。
int color[VMAX];
int V;
bool judge(int now, int c){
for(int i=head[now]; i!=-1; i=head[i]){
int v = e[i].to;
if(color[v]==0 && !judge(v,-c))
return false;
if(color[v]==c)
return false;
}
return true;
}
for(int i=0;i
给定起点 s 和终点 t,求边权值和最小的路径。
d[i] = min{d[j] + e(j,i) }
思路:
【初始化】将所有d[j] 设为 INF,除了d[s]为0
【不断更新】d的值,直到无法再更新
复杂度是O(|E|*|V|)
struct edge{int from,to,cost;};
edge es[EMAX];
int d[VMAX];
int V,E;
void bellman_ford(){
for(int i=0; i
每一次迭代,至少能确认一个点。
而我们已经确认了起点位置,因此最多要进行|V|-1次迭代(while true循环)——所有点一直线的情况。
如果第|V|次依然更新,说明存在【负环】,bellman-ford判负环:
【初始化】d都为0。
bool bellman_ford_negative_loop(){
memset(d,0,sizeof(d));
for(int j=1; j<=V; j++){
for(int i=0; i
**【没有负环】**的情况下考虑:d[ j ] = d[ i ] + e( i, j )
每次循环检查所有边太浪费时间。修改:
找到最短距离已经确定的点,从它出发进行更新。
【最短距离已定的点】距离d[i]最小的点。
复杂度 O ( ∣ V ∣ 2 ) O(|V|^2) O(∣V∣2)
int cost[VMAX][VMAX];
int d[VMAX];
bool used[VMAX];
int V;
void dijkstra(){
fill(d, d+V, INF);
fill(used, used+V, false);
d[s] = 0;
while(true){
//在未使用的顶点中选择距离最小的
int v = -1;
for(int u = 0; u < V; u++){
if(!used[u] && (v==-1 || d[u]
每次循环的首先操作:实质是每次找到最优的点并删除,这个部分可以用优先队列优化。
更新数据的操作要|E|次,复杂度 O ( ∣ E ∣ l o g ∣ V ∣ ) O(|E|log|V|) O(∣E∣log∣V∣)
struct edge{int to,cost};
typedef pair P;
int V;
vector G[VMAX];
int d[VMAX];
void dijkstra(int s){
//小顶堆,小的在上面
priority_queue, greater
> pq;
fill(d, d+V, INF);
d[s] = 0;
pq.push(P(0,s));
while(!pq.empty()){
P p = pq.top();
pq.pop();
int v = p.second;
if(d[v] < p.first) continue;
for(int i=0; id[v]+e.cost){
d[e.to] = d[v]+e.cost;
pq.push(P(d[e.to], e.to));
}
}
}
}
再次记住dijkstra的条件是【没有负环】
最差:O(|V|*|E|)
struct edge{int to,cost};
int V;
vector G[VMAX];
int d[VMAX], inq[VMAX];
void spfa(int s){
queue q;
fill(d, d+V, INF);
fill(inq, inq+V, 0);
d[s] = 0;
q.push(s);
while(!q.empty()){
int u = q.front();
q.pop();
inq[u] = 0;
for(int i=0; ie.cost+d[u]){
d[e.to]=e.cost+d[u];
if(!inq[e.to]){
inq[e.to]=1;
q.push(e.to);
}
}
}
}
}
d[i] [j] = min(d[i] [j] , d[i] [k] + d[k] [j])
【可以处理负权边】判断d[i] [i] 为负数的点
复杂度: O ( ∣ V ∣ 3 ) O(|V|^3) O(∣V∣3)
int d[VMAX][VMAX]; //d[i][i]=0, 有边时边权,无边时INF
int V;
void floyd(){
for(int i=0; i
以dijkstra为例,记录前向点prev,存入数组将其reverse就是答案。
int cost[VMAX][VMAX];
int d[VMAX];
bool used[VMAX];
int V;
//记录前向点
int prev[VMAX];
void dijkstra(int s){
fill(d, d+V, INF);
fill(used, used+V, false);
//初始化-1
fill(prev, prev+V, -1);
d[s] = 0;
while(true){
//在未使用的顶点中选择距离最小的
int v = -1;
for(int u = 0; u < V; u++){
if(!used[u] && (v==-1 || d[u] d[v]+cost[v][u]){
d[u] = d[v]+cost[v][u];
prev[u]=v;
}
}
}
}
vector getpath(int t){
vector path;
while(t!=-1){
path.push_back(t);
t=prev[t];
}
reverse(path.begin(), path.end());
return path;
}
生成树:无向图中某个子图中,任意两个顶点都互相连通且是一棵树。
最小生成树:使得边权和最小的生成树。
【思想】:对于当前树T,不断贪心地选取T和其他顶点之间最小权值的边,并将其加入T。
(充满dijkstra内味)
复杂度: O ( ∣ V ∣ 2 ) O(|V|^2) O(∣V∣2) 优先队列优化: O ( ∣ E ∣ l o g ∣ V ∣ ) O(|E|log|V|) O(∣E∣log∣V∣)
int cost[VMAX][VMAX];
int mincost[VMAX];
bool used[VMAX];
int V;
int prim(){
fill(mincost, mincost+V, INF);
fill(used, used+V, false);
mincost[0] = 0;
int res = 0;
while(true){
int v = -1;
for(int u = 0; umincost[v]+cost[v][u]){
mincost[u] = mincost[v]+cost[v][u];
}
}
}
return res;
}
按照边权从小到大check一遍,不产生圈就加入。
【是否产生圈】:用并查集高效查看是否在同一个连通分量中
复杂度 O ( ∣ E ∣ l o g ∣ V ∣ ) O(|E|log|V|) O(∣E∣log∣V∣)
struct edge{int u, v, cost;};
bool cmp(const edge& e1, const edge& e2){
return e1.cost
例1:次短路 POJ 3255
有R个道路,N个路口,双向通行、问从1号路口到N号路口的次短路长度。
1<=N<=5000, 1<=R<=100000
思路:【次短距离】d2[v] = d[u]+e(u,v) OR d2[u]+e(u,v),
因此queue中最短路和次短路都要加入
【这题四年前没AC,就是没理解最短路和次短路都要加队列这一点!】
#include
#include
#include
#include
using namespace std;
const int VMAX=5050;
const int INF=0x3f3f3f3f;
typedef pair P;
struct edge{
int to,cost;
edge(int a,int b):to(a),cost(b){}
};
vector G[VMAX];
int d[VMAX], d2[VMAX];//次短距离d2[v]=d[u]+e(u,v) 或者 d2[u]+e(u,v)
bool used[VMAX];
int R,V;
void dijkstra(int s){
fill(d, d+V, INF);
fill(d2, d2+V, INF);
priority_queue,greater
>pq;
pq.push(P(0,s));
d[s] = 0;
while(!pq.empty()){
P p = pq.top();
pq.pop();
int u = p.second, dist = p.first;
if(d2[u] d[e.to] && dv
例2:征兵顺序 POJ3723
需要召集N个女兵,M个男兵。
给出若干男女之间的亲密度,征募某个人的费用=10000-已招募人员亲密度最大值。
通过适当的招募顺序使得总费用最低。
1<=N,M<=10000, 0 ≤ R ≤ 50,000,
0 ≤ xi < N, 0 ≤ yi < M, 0 < di < 10000
思路:最小生成树裸题。
#include
#include
#include
using namespace std;
const int EMAX = 50050, VMAX=20020;
struct edge{int u, v, cost;};
bool cmp(const edge& e1, const edge& e2){
return e1.cost
例3 排列牛 POJ3169
有N头牛,编号1-N。按顺序拍成一排。
有些牛关系好(AL,BL,DL)距离不超过DL,有些牛关系不好(AD,BD,DD)距离不小于DD。
求1和N的牛的最大距离。不存在输出-1。无限大输出-2。
2 <= N <= 1,000, 1 <= A < B <= N,1 <= D <= 1,000,000
思路:
BL<=AL+DL, AL向BL连一条DL
BD>=AD+DD,即 AD<=BD-DD, BD向AD连一条-DD
另外有B>=A,即A<=B+0,B向A连0
求最短路,若有负环-1,若为INF则-2,其他情况正常输出。
#include
#include
using namespace std;
const int INF = 0x3f3f3f3f;
const int EMAX = 30020, VMAX=1010;
struct edge{int from,to,cost;};
edge es[EMAX];
int d[VMAX];
int V,E;
int bellman_ford_negative_loop(){
fill(d,d+V+1,INF);
d[1]=0;
for(int j=1; j<=V; j++){
bool update = false;
for(int i=0; i