图论题
待补充:
ACM
欧拉回路
推荐资料
注意事项
-
判断一个点是割点要求是存在 \(low[v]\ge dfn[u]\) 即可,还有是根节点且独立的子树至少两个。判断\((u,v)\) 是割边要求是 \(low[v]>dfn[u]\)。
-
点双联通需要计入 \(u\),但不需要将其弹出栈,注意要判断孤点。边双连通不需要计入 \(u\)。注意最后要把栈中剩余的点拿出来组成新的边双。
-
点双有两种定义。一种定义是“图中任意两不同点之间都有至少两条点不重复的路径”,而另外一种是“不存在割点的图”,一般是后者。
-
一个点只属于一个边双,但可能属于多个点双。
-
在双联通分量中都是加入新的分量考虑 \(u\) 的儿子 \(v\),而强联通分量加入新的分量是考虑 \(u\),条件是 \(low[u]=dfn[u]\)。
-
欧拉路径模板中一定要注意 \(head[u]\) 要动态维护,\(i\) 也要。
trick 整合
-
很多时候,要算出每个点到多个两个的最短路,比如说 P9331 [JOIST 2023] 护照 / Passport ,可以从端点开始跑最短路,算出不同的 \(dis\),并且令 \(d_i=\sum dis\),但是由于可能会有路径重叠,所以 \(d_i\) 并不一定是最终的答案,我们要把所有的 \(d_i\) 拿出来再跑一遍关于 \(d_u\) 的最短路,这样就能正确一点了。如果端点是 3、4 个或者更多,可以将不同的 \(d_u\) 在进行合并,比如说 P1002. 四向联结(crosslink)。
-
在带环图上任意走,可以走重复的点和重复的边,还有些异或、取模等内容。我们可以从简单路径或者生成树方向考虑,然后思考我们关心的是什么,环的影响。
题目
P1989 【模板】无向图三元环计数
题目大意
给定一张无向图,求其中三元环个数。
\(1\le n\le 10^5,1\le m\le 2\times 10^5\)。
解题思路
标准解法
我们考虑将其定向成有向图,优先由度数小的连向度数大的,如果度数一样就编号小的连向编号大的。容易发现,形成的新图一定是个 DAG。
我们考虑在新图上做,枚举 \(u\) 的有向边 \((u,v)\),判断是否存在 \((v,w)\) 满足存在 \((u,w)\)。这个可以在枚举 \(u\) 的时候先给 \(u\) 能到的 \(w\) 打上标记,然后枚举 \(v\) 判断即可。由于每一条边 \((u,v)\) 对答案的贡献是 \(out_v\)。复杂度是 \(O(\sum_{i=1}^m out_{v_i})\)。能到 \(O(m\sqrt m)\)。
分析一下复杂度,显然跟出度有关。对 \(v\) 根号分治一下:
- 原图上 \(v\) 的度数 \(\le \sqrt m\),新图上的出度不可能更大,故 \(out_v\le \sqrt m\)。
- 原图上 \(v\) 的度数 \(> \sqrt m\),由于我们的连边规则,\(v\) 连向的点一定都是度数 \(>\sqrt m\),所以最多只能连向 \(m\over \sqrt m\),故 \(out_v\le \sqrt m\)。
所以总复杂度是 \(O(m\sqrt m)\)。
bitset
如果你想不到正解那样的定向。直接用 bitset 来优化枚举判断 \((u,v),(v,w)\) 是否有 \((u,w)\),用 & 还有 count 之后时间复杂度是 \(O({nm\over w})\)。很能过,但是空间复杂度爆炸了,\(O({n^2\over w})\),这是存不下的。
由于这是领接矩阵,所以它必然有很多位置是浪费的了,我们尝试根号分治一下,对于度数 \(>B\) 的位置存,否则暴力找,这样就对了,时间复杂度 \(O({nm\over w}+mB)\),空间复杂度 \(O({nm\over Bw})\)。
启发:
- 正解记住小的连向小的即可。
- bitset怎么这么牛。
P9331 [JOIST 2023] 护照 / Passport \(\checkmark\)
题目大意
有 \(N\) 个国家,编号 \(1\sim N\)。国家 \(i\) 签发的护照可进入区间 \([L_i,R_i]\) 内的所有国家,且保证 \(L_i\le i\le R_i\)。旅行者初始无护照,可重复执行:获取当前所在国签发的护照;用已有护照前往某个国家。给定 \(Q\) 个可能的初始居住国 \(X_1,\dots,X_Q\),对每个 \(X_j\) 求环游所有国家所需的最少护照种类数,若不能则输出 \(-1\)。
\(2\le N\le 2\times10^5,\ 1\le Q\le N,\ 1\le L_i\le i\le R_i\le N,\ X_j\) 严格递增。
解题思路
观察 \(l_i\le i\le r_i\) 这个限制,容易发现,如果 \(i\) 能走到 1,那么它一定也能走到 \(1\sim i-1\),同理 \(n\) 也是如此,那么我们其实可以考虑 \(i\) 能否走到 \(1,n\) 即可,把这抽象成一个图论问题,那么这就是一个最短路问题了,我们建反图,算出从 1 开始的最短路,还有从 \(n\) 开始的最短路,然后加在一起即可,注意需要用到线段树优化建图。
不过有一个问题,就是可能单纯的 \(dis1_u+disn_u\) 并不是对的,因为可能有重复情况,那么我们其实可以把所有的 \(d_u=dis1_u+disn_u\) 拿出来作为源点,再跑一遍最短路即可,注意可以用 01bfs 来实现最短路,复杂度是 \(O(n\log n)\)。
启发:
-
本题主要考察将其转化为图论上问题的能力,并运用线段树优化建图的技巧。
-
很多时候,要算出每个点到多个两个的最短路,可以从端点开始跑最短路,算出不同的 \(dis\),并且令 \(d_i=\sum dis\),但是由于可能会有路径重叠,所以 \(d_i\) 并不一定是最终的答案,我们要把所有的 \(d_i\) 拿出来再跑一遍关于 \(d_u\) 的最短路,这样就能正确一点了。如果端点是 3、4 个或者更多,可以将不同的 \(d_u\) 在进行合并,比如说 P1002. 四向联结(crosslink)。
-
双倍经验 已完成。
P1002. 四向联结(crosslink) \(\checkmark\)
题目大意
有一个 \(N\times M\) 的网格,每个格子有状态(是否为陆地)和改造费用(由字符决定)。可将若干空格子改造成陆地并支付对应费用。要求通过改造使网格中存在一个“四向联结”:即一个连通的陆地集合,且至少触及上、下、左、右四个边界。求最小总费用。
\(2\le N,M\le 10^3\),字符为数字或大写字母;特殊性质 A:\(N\times M\le 100\),特殊性质 B:所有字符为 \(0\) 或 \(1\)。
解题思路
左右上下各跑一次dijkstra,再分为左上、右上再跑两次 dijkstra,最后统计答案即可。具体实现看代码。
#P1099. 图论 (graph) \(\checkmark\)
题目大意
维护带非负边权的无向图,支持加边和询问。询问给定 \(u,v,x,b,c\),问等差数列 \(x,x+b,\dots,x+(c-1)b\) 中有多少个数与某条从 \(u\) 到 \(v\) 的路径长度模 \(m\) 同余。路径可重复经过点或边,不要求简单路径。
\(1\le n,q\le 10^5,\ 1<m\le 10^9,\ 1\le w\le m,\ 0\le x,b<m,\ 1\le c\le 10^9\),操作 2 保证 \(u\ne v\)。
解题思路
首先肯定要判断连通性,用一个并查集即可。考虑没一个连通块。
对于连通块内两个点 \(u,v\),考虑他能凑出什么样的路径长度。我们首先可以在块内跑出生成树,不考虑环我们发现路径长度肯定长成 \(dep_u+dep_v-2dep_{lca}+\sum 2w\),这里的 \(w\) 是连通块内所有边的边权。其实也就是 \(dep_u+dep_v-2dep_{lca}+\sum 2w\)。
如果加上环呢?我们发现每个环我们都可以直接加上,为什么呢?

因为红边我们可以单独跑若干次,比如说 \(m\) 次抵消它的贡献,所以我们对于每个这样的环我们都可以跑任意次,那么答案就变成了 \(dep_u+dep_v+\sum_i 2k_iw+\sum_{x,y存在非树边 w} t_i(dep_x+dep_y+w(x,y))\mod M\),我们移一下项运用裴蜀定理发现最终答案一定形如 \(dep_u+dep_v+kg\),其中 \(g=\gcd(M,2w_1,2w_2,\cdots,dep_x+dep_v+w(x,y),\cdots)\),这些东西是可以动态维护的,如果两个不同连通块加边,我们直接启发式合并,对 \(siz\) 小的重新跑一遍生成树,复杂度就是启发式合并的复杂度,考虑每个点会被合并重构统计多少次,因为每次合并至少使得 \(siz\times 2\),所以不会超过 \(\log_2\) 次,总复杂度就是 \(O(n\log n)\);如果是同一连通块加边,那么就是非树边,那么一定会多一个环,将他统计入 \(g\) 即可。
现在回到问题上,统计等差数列上与其同余的个数,即 \(x+tb= dis_u+dis_v+kg\)。移一下项就是 exgcd 可以解决的题目:
这个显然就是 \(exgcd\) 求出最小的 \(t\) 然后每次增加 \(k\over \gcd(b,g)\),很容易算出有多少个。
总复杂度 \(O(n\log n)\)。
启发:
-
连通性问题肯定要先分成连通块来考虑。
-
发觉题目背后的性质,同余这些肯定要联想到裴蜀定理以及 exgcd 这些的。
-
很多这种带环图任意走的题目,往往还会带上一些类似异或、取模的运算,需要抓住相关性质,找到真正关注的量,比如这道题我们关注的就是 \(2w\) 还有环长。
P4151 [WC2011] 最大 XOR 和路径 \(\checkmark\)
题目大意
有 \(N\) 个点 \(M\) 条边的无向连通图,边有非负整数权值。求从 \(1\) 到 \(N\) 的一条可重复经过点或边的路径,使路径上边权的异或和最大,输出最大值。
\(N\le 5\times10^4,\ M\le 10^5,\ D_i\le 10^{18}\)。
解题思路
与上一道题类似的,先找出一条简单路径,考虑异或的性质,如果一条边重复经过 2 次,那么相当于没有经过。
如果一条简单路径往外挂着一些环,那么我们需要考虑的是环长,环柄是没有贡献的。如果有多条简单路径,那么这些简单路径之间肯定会形成环,把这些环拿出来,一条简单路径可以异或上这若干个环得到其它简单路径。
异或最大值考虑使用线性基,我们只需要把一条简单路径和所有的环的线性基做一下即可。找所有的环可以用 \(dfs\) 轻松完成。
QOJ10202. 异域……古城 \(\checkmark\)
题目大意
给定 \(n, l\) 和 \(m\) 个三元组 \((l_i, r_i, w_i)\)。定义 \(G_k\) 为:
- 新建一个 \(n \times (k+1)\) 个点的图。
- 对于每个三元组 \((l_i, r_i, w_i)\),在图上加入 \(k\) 条边:
- 第 \(j\) 条边连接节点 \(l_i + (j-1) \times n\) 和节点 \(r_i + j \times n\),边权为 \(w_i\)。
- 所有边加入完毕后,\(G_k\) 即为该图最小生成树的边权和。
你要求出 \(G_1 \sim G_l\)。给定的 \(m\) 个三元组保证上述生成的所有图连通。
\(1 \le n,l \le 10^5, 1 \le m \le 2 \times 10^5, 1 \le w_i \le 30\)
解题思路
非常显眼的 \(w_i\le 30\),所以我们可以考虑在值域上搞事情。有关值域的 MST 算法是 Kruskal,我们运用其连通块的思想,定义 \(d_i\) 表示保留 \(w\in[1,i]\) 的连通块个数,容易得到 \(ans=\sum_{i=1}^{30} i\times(d_{i-1}-d_{i})\),直接对这个做即可,也可以转化一下变成 \(ans=\sum_{i=1}^{30} d_{i}+n-31\)。
我们考虑如何算出 \(d_w\),我们实际要算的就是 \(1\sim i\) 的连通信息,我们考虑每新增一层会发生什么变化,第 \(i+1\) 层 \(1\sim n\) 的连通性一定不弱于第 \(i\) 层 \(1\sim n\) 的连通性,所以我们只需要考虑增多第 \(i+1\) 层连通信息的增量,并且实际上我们只需要考虑两层,也就是第 \(i\) 层和第 \(i+1\) 层,我们每次可以从前面继承一些信息过来。
具体来说,对于每个 \(w\) 开个结构体维护 \(1\sim w\) 边权的联通信息,\(all\) 表示当前的连通块个数,初始为 \(2n\),\(sum\) 表示包含当前层的点的连通块数量,也就是有多少个连通块至少有一个数位于最后一层,每次就是合并,\(all,sum\) 都可以动态维护,这个要考虑最后一层和倒数第二层。还要维护一个边集,用来维护最后一层新增的连通信息,因为每一层都是有前面的继承过来,所以边集每次只用维护最后一层即可。
通过以上方法可以维护增量(增加的连通块数量,即 \(all-sum_{last}\)),初始化 \(d_i=0\),具体实现看看代码,到时候补完在贴,也可以学一下 wjr 的,这个就是 wjr 讲的。
code
struct dsu{
int fa[N],all,sum;
struct edge{
int u,v;
};
vector<edge> G,g;
void bld(){
for(int i=1;i<=2*n;i++) fa[i]=i;
all=2*n,sum=n; //总连通块数量,包含当前层的点的连通块数量
}
int find(int u){return u==fa[u] ? u : fa[u]=find(fa[u]);}
void merge(int u,int v){
u=find(u),v=find(v);
if(u==v) return;
if(u<v) swap(u,v); //保证 u>v
fa[v]=u,all--; //总连通块数量减少
if(u>n && v>n) g.pb({u-n,v-n}), sum--;
}
int slv(){
int now=sum; //当前层原本独立的连通块数量
for(edge e:G) merge(e.u,e.v); //合并
swap(g,G),g.clear();
return all-now; //合并完后的多的连通块数量
}
}a[35];
signed main() {
IOS;
cin>>n>>l>>m;
for(int i=1;i<=30;i++) a[i].bld();
for(int i=1;i<=m;i++){
int u,v,w;
cin>>u>>v>>w;
for(int j=w;j<=30;j++) a[j].G.pb({u,v+n});
}
int ans=31*n-31; //for(int i=0;i<=30;i++) d[i]=n;
for(int i=1;i<=l;i++){
ans+=n;
for(int j=1;j<=30;j++) ans+=a[j].slv();
cout<<ans<<"\n";
}
return 0;
}
启发:
- 关注到了 \(w\) 很小这一关键信息,但并不会用,这道题很巧妙,运用了 Kruskal 本质思想,加上一系列细致的观察。

浙公网安备 33010602011771号