第一周集训总结
自我感觉: 不太行,可能是因为这周学的图论吧,没怎么接触过。
知识点总结:匈牙利算法
模板:
bool Find(int v)
{
for (int i = 1; i <= n; i ++ )
{
if (map[i][v] && road[i] != T)
{
road[i] = T;
if (link[i] == 0 || Find(link[i]))
{
link[i] = v;
return true;
}
}
}
return false;
}
大致理解:在邻接矩阵中找到与 $v$ 匹配的点,如果找到的点 $i$ 没有被匹配过,就与 $v$ 匹配上。如果找到的点 $i$ 已经与其他的点 $link_i$ 匹配上了,就看看与找到的点 $i$ 匹配的点 $link_i$ 能不能再找一个其他点匹配。如果能找到就把 $v$ 与 $i$ 匹配上。
其实还挺容易理解的。
知识点总结:Tarjan算法
模板:
void Tarjan(int v)
{
dfn[v] = low[v] = ++ cnt;
s.push_back(v), instack[v] = true;
for (int i : g[v])
{
if (!dfn[i])
{
Tarjan(i);
low[v] = min(low[v], low[i]);
}
else if (instack[i])
{
low[v] = min(low[v], dfn[i]);
}
}
if (dfn[v] == low[v])
{
int i;
scc ++ ;
do
{
i = s.back();
s.pop_back();
instack[i] = false;
id[i] = scc;
}
while (v != i);
}
}
数组解释: 用 $dfn_i$ 表示编号为 $i$ 的节点在 DFS 过程中的访问顺序 号(也可以叫做开始时间或者时间戳)
用 $low_i$ 表示 $i$ 节点及其下方节点所能到达的开始时间 最早的节点的开始时间。初始时 $dfn_i = low_i$
用 $instack_i$ 表示编号为 $i$ 的节点在 DFS 过程中是否入栈(被算过)
Tarjan中的缩点很重要,在套其他算法模板时常用,比如拓扑排序
其他自学知识点:并查集
void init(int x)//初始化函数,对录入的 n个结点进行初始化
{
for (int i = 1; i <= x; i ++ )
{
a[i] = i;//每个结点的上级都是自己
t[i] = 1;//每个结点构成的树的高度为 1
}
}
int find(int x)//改进查找算法:完成路径压缩,将 x的上级直接变为根结点,那么树的高度就会大大降低
{
if (a[x] == x) return x;//递归出口:x的上级为 x本身,即 x为根结点
return a[x] = find(a[x]);//此代码相当于先找到根结点 rootx,然后 a[x]=rootx
}
bool join(int x, int y) //合并x、y
{
x = find(x);//寻找 x的代表元
y = find(y);//寻找 y的代表元
if (x == y) return false;//如果 x 和 y 的代表元一致,说明他们共属同一集合,则不需要合并,返回 false,表示合并失败;否则,执行下面的逻辑
if (t[x] > t[y]) a[y] = x;
else
{
if (t[x] == t[y]) t[y] ++ ;//如果 x的高度和 y的高度相同,则令 y的高度加1
a[x] = y;//让 x 的上级为 y
}
return true;//返回 true,表示合并成功
}
bool isSame(int x, int y)//判断两个结点是否连通
{
return find(x) == find(y);//判断两个结点的根结点(即代表元)是否相同
}
挺简单的
其他自学知识点:拓扑排序
for (int i = 1; i <= n; i ++ )//寻找入度为零的点;
{
if (!in[i])//入度为0;
{
sum[i] = 1;
q.push(i);//加到队列当中;
}
}
while (!q.empty())
{
int u = q.front();
q.pop();
for (int i : g[u])//枚举以该点为起点的所有线段的终点;
{
in[i] -- ;//将这个点的入度-1(因为目前要删除第u个点);
sum[i] = (sum[i] + sum[u]) % mod;//更新到下一个点的路径数量;
if (!in[i])
{
q.push(i);//如果这个点的入度为零了,那么加入队列
}
}
}
也挺好理解