学图论

Boruvka

每一轮操作,对于每个点来说,让他和“最近的与他有连边且还未连通的点”相连。

最多 \(\log n\) 轮,每轮 \(O(n\cdot p)\)\(p\) 为找“最近的与他有连边且还未连通的点”的复杂度。

\(O(np\log n)\)

Kruskal 重构树

设从小到大加边,性质:

  • 二叉树,\(2n-1\) 个点(数组开两倍)
  • 原图的节点对应树中的叶子
  • 若忽略叶子,重构树满足堆的性质,即父亲 \(\ge\) 儿子
  • \(u,v\) 之间所有路径的“经过边权最大值”的最小值,是 \(u,v\) 在重构树上 LCA 的权值
  • 原图 \(u\) 点,其只经过 \(\le w\) 的边能到达的点,为 \(u\) 在树上最高的权值 \(\le w\) 的祖先的子树内所有叶子,找到这个点可倍增。

他的性质比原图最小生成树的性质,要好得多。

同余最短路

问题:给定一些数 \(a_i\),求某范围内的数有多少数可用 \(a_i\) 完全背包得出。

\(r\) 能被表示出,则 \(r+k\cdot a_i\) 也能被表示出,故固定一个 \(a_t=m\) 作为模数,对 \(x\in[0..m-1]\) 算最小的能被凑出的 \(p\equiv x\pmod m\) 是多少。

建图:对所有 \(i\)\(x\to(x+a_i)\bmod m\) 连权值为 \(a_i\) 的边,跑最短路即可得出所求。

转圈做法:考虑模 \(m\) 意义下的完全背包,考虑新加入一个 \(a_i\),显然他在长度为 \(m\) 的环上形成 \(\gcd(a_i,m)=d\) 个子环,对每个子环分别顺序转移两圈即可;或者从子环上权值最小的点开始顺序转移一圈即可(写起来不如前者简洁)。\(O(nm)\)

个人觉得转圈做法更自然。

Johnson

多源最短路,支持负边,\(O(nm\log m)\)

考虑对每个点 \(i\)\(h_i\),令 \(w'(u,v)\gets w(u,v)+h_u-h_v\),求出新图 \(s\to t\) 最短路后减去 \(h_s-h_t\) 即可求出原图最短路。

列出条件:\(w(u,v)+h_u-h_v\ge 0\),即 \(h_u+w(u,v)\ge h_v\),发现令 \(h_u\)\(d_u\) 即可。

故令 \(w(S,i)=0\),跑 SPFA(顺便判负环),改边权得新图后跑 \(n\) 次 Dijkstra 即可。

最短路树

\(v\) 的最短路是由 \((u,v)\) 松弛而来,则在生成树中保留 \((u,v)\)

性质:树上 \(s\to v\) 的路径长度为 \(s\to v\) 的最短路。

最短路图

\((u,v,w)\) 满足 \(dis_u+w=dis_v\),则保留这条边。

相当于对每个点 \(v\) 保留图上所有 \(s\to v\) 的最短路。

性质:

  • \(dis\) 定向后是个 DAG
  • 他的所有生成树都是最短路树

\(dis(s,u)+w+dis(v,t)=dis(s,t)\),则 \((u,v,w)\) 可以在 \(s\to t\) 的最短路上。

欧拉回路

看代码就懂了。

int tp, stk[N];
void Eul(int u) {
	for (int &i = cur[u]; i < (int)G[u].size(); ) Eul(G[u][i++]);
	stk[++tp] = u;
}

Eul(s);
while (tp) cout << stk[tp--] << ' ';
cout << '\n';
  • 欧拉路径:一笔画
  • 欧拉回路:一笔画,起点等于终点
  • 欧拉图:存在欧拉回路
  • 半欧拉图:存在欧拉路径,但不是欧拉图

欧拉图判定:

  • 有向图:弱连通,入度等于出度
  • 无向图:连通,每个点度数为偶数

半欧拉图判定:

  • 有向图:弱连通,存在两个点入度分别等于出度加一、出度减一,其余点入度等于出度
  • 无向图:连通,恰好两个点度数为奇数

连通性

\(\text{low}(u)\)\(u\) 子树内能一步走到的最小 dfn

  • 边双:割边,每个点恰属于一个边双连通分量
  • 点双:割点,每条边恰属于一个点双连通分量

Menger 定理:

  • 边形式:使 \(u,v\) 不连通所需删去的边数最小值,等于 \(u,v\) 之间边不交的路径的数量的最大值。
  • 点形式:使 \(u,v\)(不相邻)不连通所需删去的点数最小值,等于 \(u,v\) 之间点不交(不含 \(u,v\))的路径的数量的最大值。

证明可以看 Alex-Wei 的。这个定理与最大流-最小割定理类似。

于是我们可以定义:

  • 局部边连通度:使 \(u,v\) 不连通所需删去的边数最小值;
  • 局部点连通度:使 \(u,v\) 不连通所需删去的点数最小值(\(u,v\) 不相邻);
  • \(k\)-边连通:\(u,v\) 边连通度 \(\ge k\)
  • \(k\)-点连通:\(u,v\) 点连通度 \(\ge k\)

圆方树

普通圆方树是只支持仙人掌的;这里默认的圆方树是广义圆方树。

建法:若 \(\text{low}(v)\ge\text{dfn}(u)\),说明 \(v\) 子树在栈内的部分与 \(u\) 形成了点双,新建方点并连边、弹栈直到 \(v\) 被弹出即可,注意两倍空间。

圆方树完整保留了原图的必经性。

支配树

对于有向图,若 \(s\to y\) 的所有路径均经过 \(x\),则称 \(x\) 支配 \(y\)

由于支配关系具有

  • 传递性:若 \(x\) 支配 \(y\)\(y\) 支配 \(z\),则 \(x\) 支配 \(z\)
  • 自反性:\(x\) 支配 \(x\)
  • 反对称性:若 \(x\) 支配 \(y\)\(y\) 支配 \(x\),则 \(x=y\)

这保证了支配是偏序关系,这让他能建出 DAG,但不够优秀。

性质:若 \(x,y\) 均支配 \(z\),则 \(x,y\) 之间也有支配关系。

注意到这样就可以建树了:考虑支配 \(y\) 的所有点 \(D_y=\{x_1,x_2,\ldots,x_k\}\),则 \(D_y\) 内所有元素形成了全序关系,即任意两个元素之间均可比较,所有 \(x_i\) 的关系可以用一条链描述,这就是 \(y\) 的祖先链。

\(f(i)\) 表示 \(D_i\) 当中被其他所有点支配的 \(x_j\),在 \(i\)\(f(i)\) 之间连边,这就是支配树。

支配树用于描述有向图在给定起点时节点的必经性。

对于 DAG,我们可以 \(O((n+m)\log n)\) 求出其支配树:

按拓扑序处理,若 \(x\) 有入边 \(y_i\to x\),则

\[ f(x)=LCA(y_1,y_2,\ldots,y_k) \]

动态更新倍增数组即可。

DAG 链剖分

若原图有若干无入度点,新建 \(1\) 向他们连边,这样只有 \(1\) 没有入度。

\(f(i)\) 为从 \(i\) 出发的路径条数。定义重儿子为 \(f\) 值最大的后继,这样对于任意轻边 \(i\to j\)\(2f(j)\le f(i)\)。这可以解决部分题目,但不够优秀,我们想要 \(i\to son(i)\) 形成的是链而不是树。

\(g(i)\)\(1\to i\) 的路径条数。设 \(i\to j\) 为重边当且仅当 \(j\)\(i\)\(f\) 值最大的后继,且 \(i\)\(j\)\(g\) 值最大的前驱。这样形成了若干条链。走一条轻边,要么 \(f\) 减半,要么 \(g\) 翻倍,故一条路径上的轻边条数为 \(\log V\) 级别,\(V\) 为总路径条数。这说明只有当路径条数不大时 DAG 链剖分才适用

DAG 链剖分常与 SAM 结合,因为此时路径条数为 \(s\) 的本质不同子串数,为 \(O(n^2)\) 级别,且剖分后可以快速定位字符串查询相关信息。更重要的是可以通过 \(\log\) 条时间戳连续的链描述 \(s[l,r_1]\)\(s[l,r_2]\) 的路径,非常有用。

Dilworth 定理

一个偏序集的最大反链大小等于其最小链覆盖。

考虑一个长度为 \(n\) 的排列 \(p\)。以下两个结论至少有一个成立:

  • 存在一个长度为 \(\sqrt n\) 的递增序列
  • 存在一个长度为 \(\sqrt n\) 的递减序列

Hall 定理

设一个二分图的两部点数为 \((x,y),x<y\),其的一个完美匹配定义为左部 \(x\) 个点成为匹配点。

该二分图存在完美匹配的充要条件是,对于左部点的大小为 \(k\) 的任意子集 \(S\),这些点在右部点连到的点集(也被称为 \(S\) 的邻域,记为 \(N(S)\))大小不小于 \(k\)

推论:一个任意二分图 \((V,E)\) 的最大匹配为

\[ |V|-\max_{S\subseteq V}\{|S|-|N(S)|\} \]


习题会添加在这里面,附带简要题解。

题目选做

1. CF1965F Conference

Statement

二分图匹配,但是左边每个人连向的都是一段区间,\(\forall k\in[1..n_1]\),输出从右边点选任意长度为 \(k\) 的区间和左边所有人做匹配,匹配数能达到 \(k\) 的区间的个数,\(n_1,n_2\le2\cdot10^5\).

Solution

判断右边点单个区间 \([l,r]\),考虑 Hall 定理,设 \(N(S)\) 为右边点 \(S\) 对应的左边点集大小,\(S\) 合法当且仅当 \(|N(S)|\ge|S|\),那么 \([l,r]\) 合法当且仅当 \(\forall S\subseteq[l,r],S\) 合法.

猜测只用判断成一段区间的 \(S\),但是会被以下数据叉掉:

3
1 3
2 2
2 2

对于 \([1,3]\),只有 \(\{1,3\}\) 不合法.

于是考虑处理初始的左边点的线段,发现对于 \([a,b],[a,c](b\le c)\),把他们换成 \([a,b],[a+1,c]\) 不影响结果(\(a=b=c\) 时相当于删掉这条线段),因为对于 \(a\) 点,为了最优,他一定会选 \([a,b]\) 而非 \([a,c]\).

处理后每个人的左端点互不相同,此时找性质,发现:

  • 可以只判成一段区间的 \(S\),因为若一个不合法的 \(S\) 不是一段连续的区间,考虑将其补全成一段区间,此时他还是不合法,因为每补全一个数,\(|N(S)|\) 至多增加 \(1\),此时有只包含这个数的区间.
  • 看单调性,发现若 \([l,r]\) 不合法,\([l,r+1],[l-1,r]\) 也不合法,因为 \([l,r]\) 合法当且仅当 \(\forall S\subseteq[l,r],S\) 合法.

于是可以双指针求,对每个 \(l\) 最大的 \(r\) 使 \([l,r]\) 合法,最后差分统计即可.

Code
// LUOGU_RID: 160078999
// by:Joey_c / sto hunction orz
#include <bits/stdc++.h>
using namespace std;
#define rep(i,j,k) for(int i=j;i<=k;i++)
#define reo(i,j,k) for(int i=j;i>=k;i--)
#define debug fprintf(stderr,"Running %s on line %d...\n",__FUNCTION__,__LINE__)
#define fi first
#define se second
#define pb emplace_back
#define mp make_pair
typedef long long ll;
constexpr int N=2e5+10;

int main() {
	ios::sync_with_stdio(false), cin.tie(nullptr);
	int n,m=0,nn;cin>>n,nn=n;vector<pair<int,int>>a(n+1);vector<int>buc[N];rep(i,1,n)cin>>a[i].fi>>a[i].se,m=max(m,a[i].se),buc[a[i].fi].pb(a[i].se);
	n=0;priority_queue<int,vector<int>,greater<int>>q;
	rep(i,1,m){
		for(int j:buc[i])q.push(j);while(!q.empty()&&q.top()<i)q.pop();if(!q.empty())a[++n]=mp(i,q.top()),q.pop();
	}
	vector<int>b(m+2,0),c(m+2,0),ans(max(m,nn)+2,0);rep(i,1,n)b[a[i].fi]=1,++c[a[i].se];
	for(int s=0,l=m,r=m;l;--l){
		s+=c[l];while(r>=l&&s<r-l+1)s-=b[r--];if(r<=m)++ans[r-l+1];
	}reo(i,max(m,nn),1)ans[i]+=ans[i+1];rep(i,1,nn)cout<<ans[i]<<"\n";
	return 0;
}
posted @ 2024-11-13 19:18  Laijinyi  阅读(10)  评论(0编辑  收藏  举报