次小生成树
http://poj.org/problem?id=1679
不难得出,次小生成树可以由最小生成树更换一条边得到。
首先构造原图的最小生成树,然后枚举每一条不在最小生成树中的边 (u, v, w),尝试将这条边加入生成树,因为直接加入边会产生环,所以我们需要在加边之前删去最小生成树上 u 到 v 的路径上权值最大的边。在枚举每一条边时我们都会得到一棵生成树,这些生成树中边权和最小的即为要求的次小生成树。
需要在构造最小生成树时将完整的树结构构造出来,并且使用树上倍增算法查询两点间边权值最大的值。
强连通分量
在一个有向图中,如果某两点间都有互相到达的路径,那么称中两个点强连通,如果任意两点都强连通,那么称这个图为强连通图;一个有向图的极大强连通子图称为强连通分量。
https://oi.men.ci/tarjan-scc-notes/
https://www.cnblogs.com/stxy-ferryman/p/7779347.html
一个强连通分量中的点一定处于搜索树中同一棵子树中。
Tarjan 算法:
low[]
表示这个点以及其子孙节点连的所有点中dfn最小的值.s[]
表示当前所有可能能构成是强连通分量的点.col[]
对强连通分量进行染色.v[to[k]]==false
说明无论如何这个点也不能与u构成强连通分量,因为它不能到达u.low[x]==dfn[x]
说明u点及u点之下的所有子节点没有边是指向u的祖先的了,即u点与它的子孙节点构成了一个最大的强连通图即强连通分量.if (!dfn[i]) tarjan(i);
Tarjan一遍不能搜完所有的点,因为存在孤立点. 所以我们要对一趟跑下来还没有被访问到的点继续跑Tarjan.
均摊时间复杂度 \(O(n)\).
int dfn[N], low[N], t, s[N], st; int col[N], ct; bool v[N]; void tarjan(int x) { dfn[x]=low[x]=++t, s[++st]=x, v[x]=true; for (int k=head[x]; k; k=nex[k]) { if (dfn[to[k]]) if (v[to[k]]) low[x]=min(low[x], dfn[to[k]]); else tarjan(to[k]), low[x]=min(low[x], low[to[k]]); } if (low[x]==dfn[x]) { col[x]=++ct, v[x]=false; while (s[st]!=x) col[s[st]]=ct, v[s[st--]]=false; --st; } } for (int i=1; i<=n; ++i) if (!dfn[i]) tarjan(i);
缩点
缩点: 对于 贡献具有传递性 的题,因为强连通分量中的每两个点都是强连通的,可以将一个强连通分量当做一个超级点,而点权按题意来定.
POJ2186 Popular Cows: 告诉你有n头牛,m个崇拜关系,并且崇拜具有传递性,如果a崇拜b,b崇拜c,则a崇拜c,求最后有几头牛被所有牛崇拜。
显然一个强联通分量内的所有点都是满足条件的,我们可以对整张图进行缩点,然后就简单了。
剩下的所有点都不是强连通的,现在整张图就是一个DAG(有向无环图).
那么就变成一道水题了,因为这是一个有向无环图,不存在所有点的出度都不为零的情况。
所以必然有1个及以上的点出度为零,如果有两个点出度为零,那么这两个点肯定是不相连的,即这两圈牛不是互相崇拜的,于是此时答案为零,如果有1个点出度为0,那么这个点就是被全体牛崇拜的.
这个点可能是一个强联通分量缩成的超级点,所以应该输出整个强联通分量中点的个数。
/* 以上同 Tarjan 求强连通分量. */ int deg[N], cnt[N]; int tot=0, ans=0; for (int i=1; i<=n; ++i) { for (int k=head[i]; k; k=nex[k]) if (col[to[k]]!=col[i]) ++deg[col[i]]; ++cnt[col[i]]; } for (int i=1; i<=ct; ++i) if (!deg[i]) ++tot, ans=cnt[i]; if (!tot || tot>1) printf("0\n"); else printf("%d\n", ans);
割点、桥
无向图.
割点
特判:根节点如果有两个及以上的儿子,那么他也是割点.
int dfn[N], low[N], t, root; bool cut[N]; void tarjan(int x) { int flag=0; dfn[x]=low[x]=++t; for (int k=head[x]; k; k=nex[k]) { if (dfn[to[k]]) low[x]=min(low[x], dfn[to[k]]); else { tarjan(to[k]), low[x]=min(low[x], low[to[k]]); if (low[y]>=dfn[x]) { ++flag; if (x==root) {if (flag>1) cut[x]=true; } else cut[x]=true; } } } } for (int i=1; i<=n; ++i) if (!dfn[i]) tarjan(root=i); for (int i=1; i<=n; ++i) if (cut[i]) printf("%d ", i);
桥
邻接表存图编号从2开始. 即开头 head[0]=1;
.
int dfn[N], low[N], t; bool bdg[N<<1]; void tarjan(int x, int last) { dfn[x]=low[x]=++t; for (int k=head[x]; k; k=nex[k]) { if (dfn[to[k]]) if (i!=(last^1)) low[x]=min(low[x], dfn[to[k]]); else { tarjan(to[k], k), low[x]=min(low[x], low[to[k]]); if (low[y]>=dfn[x]) bdg[k]=bdg[k^1]=true; } } } for (int i=1; i<=n; ++i) if (!dfn[i]) tarjan(i, 0); for (int i=2; i<head[0]; i+=2) if (bdg[i]) printf("%d %d\n", to[i^1], to[i]);
欧拉函数
\[ n={\prod_{i=1}^{k}} \ {p_i}^{a_i} \]
\[ \varphi(n)=n\cdot \prod_{i=1}^{k} (1 - \frac{1}{p_i}) \]
int phi() { int m = floor(sqrt(n + 0.5)), ans = n; for (int i = 2; i <= m; i++) { if (n % i == 0) { ans = ans / i * (i - 1); while (n % i == 0) n /= i; } } if (n != 1) ans = ans / n * (n - 1); // 整体为素数 return ans; }
https://oi.men.ci/euler-sieve/
模意义下的除法
要求模数为素数。
费马小定理:
\[a ^ {p - 1} \equiv 1 \pmod p \]
\[a \cdot a ^ {p - 2} \equiv 1 \pmod p \]
inline int pow(long long x, int y) { long long res=1; for (; y; x=x*x%mod, y>>=1) if (y&1) res=res*x%mod; return res; } inline int inv(int& x) {return pow(num, mod-2); }
拓展 Euclid 算法:
在对数时间内求出方程 \(ax + by = \gcd(a, b)\) 的一组解。当 b 为素数时,\(\gcd(a, b)=1\),则
\[ax \equiv 1 \pmod b \]
式中 \(x\) 即为所求。
void exgcd(int& a, int& b, int &g, int &x, int &y) { if (!b) g=a, x=1, y=0; else exgcd(b, a%b, g, y, x), y-=x*(a/b); } inline int inv(int& t) { register int g, x, y; exgcd(t, mod, g, x, y); retuurn ((x%mod)+mod)%mod; }
全错位排列递推公式
\[f(n) = (n-1)\cdot \left[f(n-1)+f(n-2) \right]\]
常数优化
书写优化:
Before | After |
---|---|
x==0 |
!x |
x!=-1 |
~x |
x!=y |
x^y |
x*10 |
(x<<3) + (x<<1) |
x*2+1 |
x<<1|1 |
x%2 |
x&1 |
(x+1)%2 |
x^1 |
x%2==0 |
~(x&1) |
函数参数尽量取地址。
大循环分开来做。register
。(手动 cache)
strlen()
函数提前求好值,避免重复调用。
表达式合并。(手动并行)
重载运算符:
struct mat { static const int ml=10; int m[ml][ml]; mat(int x=0) { memset(m, 0, sizeof(m)); for (int i=0; i<ml; i++) m[i][i]=x; } int* operator [] (int p) {return m[p]; } };