第一次写知识总结,有什么毛病烦请诸位神仙指正(
博客里有些链接来自题目,有些来自我已经写过的博客,如果是博客我就没有再放题解了(懒得搬了~
图论两个重要的问题,一是算法的选择,二是建图。很多算法往往本身有局限性,例如Dijkstra一般不能求含负边的图的最短路,而SPFA一般又不能维护稠密图最短路。
然而图论最重要的不是算法的选择,而是建图。很多人为算法是否可用或者是否好写而常常烦恼,然而建图往往决定了你能拿多少分。
当然两者都很重要,只是近年来的考题都更加注重思维,所以建图变得更为重要。
求解最短路只是一种思想,于我而言,这其实这是在图上DP的过程
最短路的建图关键在于如何利用已知信息构造辅助的边和点,例如我们经常用到的虚点
Floyd复杂度O(N^3),适合求解多源最短路,不过这个算法近来已经不是很常考,有时候会出现倍增Floyd,或者传递闭包
例如奶牛接力这道题,倍增Floyd的板题,利用类似矩阵快速幂的思想,优化到 O ( N 3 ∗ l o g N ) O(N^3*logN) O(N3∗logN)
利用Floyd支持多次查询、修改、删边的图论题也有出现
SPFA是一个被Oiers诟病太多了的算法,号称复杂度 O ( k M ) O(kM) O(kM),实际上常常被出题人卡成sb,不过她确实还是好用,例如最短路计数,最短路Dp,最短路+二分,这几类题往往不会以稠密图的方式出现,因此SPFA 有时也能快的飞起
本身Dijkstra的复杂度其实是 O ( N 2 ) O(N^2) O(N2)的,不过用优先队列或者Set优化可以降低到 O ( N ∗ l o g N ) O(N*logN) O(N∗logN),Dijkstra的效率是有很高程度的保证的,不过也难逃诟病——求含负边权的图的最短路时,显得束手无策,当然在一定限制下也能求,不过就不讨论了。
Dijkstra除了复杂度相对稳定,还有一点就是它相对于SPFA更容易维护字典序,因为优先队列里面存的是一个二元组,第二元在第一元相同的情况下,可以保证编号小的点优先
这个其实很好办,在最短路的算法里面多维护一个dis2表示次短路即可,板题为Roadblocks
差分约束系统是基于不等式组而来的一种最短/长路问题,例如给定m个ai-aj的不等式,求a1-an的最大值
建图方式其实已经烂大街了,就是套路:求最大值,所有不等式转为"<=",对于ai-aj<=k,j向i连一条长为k的边,然后跑最短路;求最小值,所有不等式转为">=",对于ai-aj>=k,j向i连一条长为k的边,然后跑最长路
特别地,对于等式,转化成一个"<=",一个">="来连边
虽然说是套路,但放在题目当中还是一道题一个样
几道水题:[SCOI2011]糖果、[SCOI2008]天平、小K的农场
这种题一般出现在有向图中
主要就是用A* 算法优化,先跑反图求出dis,在以此和到终点的距离为估价函数求前k短路,[SDOI2010]魔法猪学院就是这样一道题
然而A* 算法并没有达到复杂度最优,因为我们只利用了距离这一信息,最优的目前是可持久化可并堆的做法,建议大家看看zxyoi神仙的博客
分层图其实没什么好讲的,其实本质就是最短路DP,只不过分层图的辅助空间用在加边上了,DP的话直接在Dis数组上加一维,例如可以免费k条边的最短路
一道经典题——逛公园
能够把图论题考到这种高度也是绝了
不过NOIP确实做到了——华容道
前置知识:并查集
这一部分理解起来本身挺容易的,不过拓展非常蛇皮,需要很多 奇技淫巧,建图的关键在于对边权的修改
Prim是一种贪心算法,对于每一个新加入树的点,要满足它到树的距离最小,每次选取最小值,直到每个点在树上,堆优化之后优化至 O ( ( N + M ) l o g M ) O((N+M)logM) O((N+M)logM),完全图较为常用
Kruskal是另一种贪心算法,按边权排序之后依次加入,不过边要能改变连通性再加,直至加入n-1条边,复杂度 O ( M l o g + M α ( N ) ) O(Mlog+Mα(N)) O(Mlog+Mα(N)),这种算法较之前者更好写,复杂度在大多数情况下表现更好
首先我们可以证明次小生成树一定在一颗最小生成树的邻集中(即只有一条边不同)。
所以我们可以先求出最小生成树,枚举每一条非树边,利用lca维护非树边两端点之间路径上的最大值和次大值,并尝试用当前非树边替换,最后打擂台比较,得到最小生成树邻集中边权和最小的一颗生成树,就是次小生成树。
详见【模板】次小生成树
即有向图的最小生成树
例如WOJ 4104
给定包含 n 个结点, m 条有向边的一个图。试求一棵以结点r 为根的最小树形图,并输出最小树形图每条边的权值之和,如果没有以 r 为根的最小树形图,输出 −1。
#include
#define re register
#define ll long long
using namespace std;
inline int rd(){
int re data=0,w=1;static char ch=0;ch=getchar();
while(!isdigit(ch)&&ch!='-')ch=getchar();
if(ch=='-')w=-1,ch=getchar();
while(isdigit(ch))data=(data<<1)+(data<<3)+(ch^48),ch=getchar();
return data*w;
}
const int N=105,M=1e4+5,inf=1e9;
struct node{int u,v,w;}e[M];
int n,m,rt,cnt,fa[N],id[N],mn[N],top[N];
inline int solve(){
ll re ans=0;
while(1){
for(int re i=1;i<=n;i++)id[i]=top[i]=0,mn[i]=inf;
for(int re i=1;i<=m;i++)if(e[i].u!=e[i].v&&e[i].w<mn[e[i].v])fa[e[i].v]=e[i].u,mn[e[i].v]=e[i].w;
int re u=mn[rt]=0;
for(int re i=1;i<=n;i++){
if(mn[i]==inf)return -1;//An Abandoned Point
ans+=mn[i];
for(u=i;u!=rt&&top[u]!=i&&(!id[u]);u=fa[u])top[u]=i;
if(u!=rt&&!id[u]){
id[u]=++cnt;//Find These Rings
for(int re j=fa[u];j!=u;j=fa[j])id[j]=cnt;
}
}
if(!cnt)return ans;//Answer Comes Out When There Is No Ring
for(int re i=1;i<=n;i++)if(!id[i])id[i]=++cnt;//Make Self-rings
for(int re i=1;i<=m;i++){
int re last=mn[e[i].v];//Contract Points
if((e[i].u=id[e[i].u])!=(e[i].v=id[e[i].v]))e[i].w-=last;//Not In The Same Ring
}n=cnt;rt=id[rt];cnt=0;
}
}
int main(){
n=rd();m=rd();rt=rd();
for(int re i=1;i<=m;i++)e[i]=(node){rd(),rd(),rd()};
cout<<solve();
return 0;
}
顾名思义,就是求suma*sumb的最小值,其中a,b为两种边权
详见Timeismoney
最优比率生成树是基于分数规划而来的一类问题
要求最优比率,可以考虑二分这个比率,用二分值修改边权,再代入求MST去验证
题目重建家园
这一部分的建图往往要与生成树、最短路(常涉及缩点操作)相联系
tarjan算法的意义在于对树的dfs序进行了有效的利用,算法实现不再赘述,因为确实在模板方面没有什么特别之处
例题:受欢迎的牛
边双例题:【模板】BCC边双连通分量、矿场搭建
点双例题:困难的图
这一类题一般都不会出现在联赛难度的题目当中,不过有些贪心题可以用网络流打打暴力,这时候建边关键在于流量的大小和点的含义
跑最大流就是暴力求解贪心的一种常规操作,不过真正解最大流还是有必要会的
虽然思想很显然,但是建边的花样非常非常多
拆点、修改边权……网络流几乎把所有的建图方式都覆盖了
例题:蜥蜴lizard
匹配类的问题在建边方面没有太大的扩展性,主要是根据限制关系建图
2-SAT问题的关键在于处理选与不选的关系
例题:Ikki’s Story IV - Panda’s Trick
二分图是图论中一种比较特殊的模型,求解这类问题时,往往要考虑与最大匹配,最小点/边覆盖等模型的联系
板子题很简单:[USACO4.2]完美的牛栏The Perfect Stall
不过加上一个线段树分治就比较毒了……:【bzoj4025】二分图&题解
LCA不会单独考,但是结合其他内容之后就会异常的毒
例如 :加一个线段树合并——【NOIP2016DAY1T2】天天爱跑步
还有之前提到过的次小生成树
拓扑排序的题一般不单独拿出来考,往往与其他图论题相联系
例如:
构造汇点(虚点)——表格
Tarjan+拓扑排序+DP——[ZJOI2007]最大半连通子图
2018年NOIPDAY2T1的旅行就是一道基环树相关问题,只不过前60pts白送,剩下40pts也只是多想一下就能到手(然而当时我太菜,并没有想到这一点,还在纠结为什么没过有环的那个样例……)
基环树的题与一般的树的题相比差异不是太大,主要是要多考虑一个环的问题,然而代码非常难写,没有固定的模板(除了找环,如果你认为那个DFS也算)
看到这里应该都知道我在说哪道题:
Journeys
题解
这方面最经典的就是墨墨的等式
利用同余的思想求了一个最短路,妙啊
题解在这儿
离散化坐标是解决坐标系中点对距离的一种建图方式,往往按照坐标上的一些关系建图可以大大降低建图复杂度。
例如:连通代价,原本 O ( N 2 ) O(N^2) O(N2)的建图复杂度,由于三个维度的坐标可以分开考虑,建图复杂度就降低至了 O ( N ) O(N) O(N)