230623 做题记录 // 强连通分量
楚天千里清秋,水随天去秋无际……
A. 时间戳
http://222.180.160.110:1024/contest/3698/problem/1
?合着强制链式前向星?邻接表党震怒!
所以决定 reverse
……
namespace XSC062 {
using namespace fastIO;
const int maxn = 15;
int dfn[maxn];
int n, m, x, y, now;
std::vector<int> g[maxn];
void DFS(int x) {
dfn[x] = ++now;
for (auto i : g[x]) {
if (dfn[i] == 0)
DFS(i);
}
return;
}
inline void add(int x, int y) {
g[x].push_back(y);
return;
}
int main() {
read(n), read(m);
while (m--) {
read(x), read(y);
add(x, y), add(y, x);
}
for (int i = 1; i <= n; ++i)
std::reverse(g[i].begin(), g[i].end());
DFS(1);
for (int i = 1; i <= n; ++i)
print(dfn[i], '\n');
return 0;
}
} // namespace XSC062
B. 割点和割边
http://222.180.160.110:1024/contest/3698/problem/2
说好的今天全是强连通分量呢,,,
namespace XSC062 {
using namespace fastIO;
const int maxn = 5e3 + 5;
int dfn[maxn], low[maxn];
std::vector<int> g[maxn];
int n, m, x, y, now, rt, cntv, cntw;
inline int min(int x, int y) {
return x < y ? x : y;
}
void DFS(int x, int fa) {
bool cut = 0;
dfn[x] = low[x] = ++now;
for (auto i : g[x]) {
if (!dfn[i]) {
DFS(i, x);
low[x] = min(low[x], low[i]);
if (low[i] >= dfn[x]) {
if (fa != -1 || ++rt >= 2)
cut = 1;
}
if (low[i] > dfn[x])
++cntw;
}
else if (i != fa)
low[x] = min(low[x], dfn[i]);
}
cntv += cut;
return;
}
inline void add(int x, int y) {
g[x].push_back(y);
return;
}
int main() {
read(n), read(m);
while (m--) {
read(x), read(y);
add(x, y), add(y, x);
}
DFS(1, -1);
print(cntv, '\n');
print(cntw, '\n');
return 0;
}
} // namespace XSC062
C. 点双连通分量
http://222.180.160.110:1024/contest/3698/problem/3
题目背景
注:本题需要链式前向星建图
好的,我用邻接表。
namespace XSC062 {
using namespace fastIO;
const int maxn = 5e4 + 5;
std::stack<int> t;
int dfn[maxn], low[maxn];
int n, m, x, y, now, rt, cntv;
std::vector<int> g[maxn], scc[maxn];
inline int min(int x, int y) {
return x < y ? x : y;
}
void DFS(int x, int fa) {
t.push(x);
dfn[x] = low[x] = ++now;
for (auto i : g[x]) {
if (!dfn[i]) {
DFS(i, x);
low[x] = min(low[x], low[i]);
if (low[i] >= dfn[x]) {
++cntv;
int p;
do {
p = t.top();
t.pop();
scc[cntv].push_back(p);
} while (p != i);
scc[cntv].push_back(x);
}
}
else if (i != fa)
low[x] = min(low[x], dfn[i]);
}
return;
}
inline void add(int x, int y) {
g[x].push_back(y);
return;
}
int main() {
read(n), read(m);
while (m--) {
read(x), read(y);
add(x, y), add(y, x);
}
DFS(1, -1);
print(cntv, '\n');
for (int i = 1; i <= cntv; ++i) {
print(scc[i].size(), ' ');
for (auto j : scc[i])
print(j, ' ');
putchar('\n');
}
return 0;
}
} // namespace XSC062
D. 边双连通分量
http://222.180.160.110:1024/contest/3698/problem/4
为什么全是板子???
woc,WA 了。
我你妈,图不连通。
namespace XSC062 {
using namespace fastIO;
const int maxn = 5e4 + 5;
int dfn[maxn], low[maxn];
std::vector<int> g[maxn];
int n, m, x, y, now, cntw;
inline int min(int x, int y) {
return x < y ? x : y;
}
void DFS(int x, int fa) {
dfn[x] = low[x] = ++now;
for (auto i : g[x]) {
if (!dfn[i]) {
DFS(i, x);
low[x] = min(low[x], low[i]);
}
else if (i != fa)
low[x] = min(low[x], dfn[i]);
}
if (low[x] == dfn[x])
++cntw;
return;
}
inline void add(int x, int y) {
g[x].push_back(y);
return;
}
int main() {
read(n), read(m);
while (m--) {
read(x), read(y);
add(x, y), add(y, x);
}
for (int i = 1; i <= n; ++i) {
if (dfn[i] == 0)
DFS(i, -1);
}
print(cntw, '\n');
return 0;
}
} // namespace XSC062
E. 绮丽的天空
http://222.180.160.110:1024/contest/3698/problem/5
强连通分量缩点然后 SPFA 或者 Topo 吧。
感觉 Topo 搭坡会好写一点。
namespace XSC062 {
using namespace fastIO;
const int maxn = 5e5 + 5;
#define mkp std::make_pair
using pii = std::pair<int, int>;
std::set<pii> u;
std::stack<int> t;
std::queue<int> q;
int a[maxn], col[maxn];
int n, m, x, y, now, cntw;
std::vector<int> g[maxn], g1[maxn];
int dfn[maxn], low[maxn], deg[maxn];
int mx[maxn], su[maxn], fm[maxn], fs[maxn];
inline int min(int x, int y) {
return x < y ? x : y;
}
inline int max(int x, int y) {
return x > y ? x : y;
}
void DFS(int x) {
t.push(x);
dfn[x] = low[x] = ++now;
for (auto i : g[x]) {
if (!dfn[i]) {
DFS(i);
low[x] = min(low[x], low[i]);
}
else if (!col[i])
low[x] = min(low[x], dfn[i]);
}
if (low[x] == dfn[x]) {
int p;
++cntw;
do {
p = t.top();
t.pop();
mx[cntw] = max(mx[cntw], a[p]);
su[cntw] += a[p];
col[p] = cntw;
} while (p != x);
}
return;
}
inline void add(int x, int y) {
g[x].push_back(y);
return;
}
inline void _add(int x, int y) {
g1[x].push_back(y);
return;
}
inline void Topo(void) {
for (int i = 1; i <= cntw; ++i) {
fm[i] = mx[i];
fs[i] = su[i];
if (deg[i] == 0)
q.push(i);
}
while (!q.empty()) {
int f = q.front();
q.pop();
for (auto i : g1[f]) {
if (fs[f] + su[i] > fs[i]) {
fs[i] = fs[f] + su[i];
fm[i] = max(fm[f], mx[i]);
}
else if (fs[f] + su[i] == fs[i])
fm[i] = max(fm[f], fm[i]);
if (--deg[i] == 0)
q.push(i);
}
}
return;
}
int main() {
read(n), read(m);
for (int i = 1; i <= n; ++i)
read(a[i]);
while (m--) {
read(x), read(y);
add(x, y);
}
for (int i = 1; i <= n; ++i) {
if (dfn[i] == 0)
DFS(i);
}
for (int i = 1; i <= n; ++i) {
for (auto j : g[i]) {
if (col[i] == col[j])
continue;
if (u.find(mkp(col[i], col[j]))
!= u.end())
continue;
u.insert(mkp(col[i], col[j]));
_add(col[i], col[j]);
++deg[col[j]];
}
}
Topo();
int su = 0, mx = 0;
for (int i = 1; i <= n; ++i) {
if (fs[i] > su)
su = fs[i], mx = fm[i];
else if (fs[i] == su)
mx = max(mx, fm[i]);
}
print(su, ' '), print(mx, '\n');
return 0;
}
} // namespace XSC062
F. Grass Cownoisseur
http://222.180.160.110:1024/contest/3698/problem/6
woc,一大堆英语吓死我,往下划了半天居然有翻译。
其实应该挺简单的,还是 SPFA 一下(注意因为规定了起点所以不能用拓扑)就好了。
因为强连通分量缩完点之后是一个 DAG,当我们反向一条边后可能会出现至多一个环,而我们需要的路径就是这个环。
接下来就要寻找反向边出现的位置。以 1 为起点跑一个 SPFA,再建个反图以 1 为起点跑一个 SPFA,这样我们就得到了 1 到达每个点和每个点回到 1 的最长路。
枚举每一条边 \((u, v)\) 作为反向边的情况,该情况下的答案就是 \(1\to v\) 和 \(u \to 1\) 的最长路之和(前提是两端点均可达 1 号点),在所有边中找到最大值即可。
namespace XSC062 {
using namespace fastIO;
const int maxn = 1e5 + 5;
#define mkp std::make_pair
using pii = std::pair<int, int>;
bool in[maxn];
std::set<pii> u;
std::stack<int> t;
std::queue<int> q;
std::vector<int> g[maxn];
int n, m, x, y, now, cntw, res;
int siz[maxn], d1[maxn], d2[maxn];
int col[maxn], dfn[maxn], low[maxn];
std::vector<int> g1[maxn], g2[maxn];
inline int min(int x, int y) {
return x < y ? x : y;
}
inline int max(int x, int y) {
return x > y ? x : y;
}
void DFS(int x) {
t.push(x);
dfn[x] = low[x] = ++now;
for (auto i : g[x]) {
if (!dfn[i]) {
DFS(i);
low[x] = min(low[x], low[i]);
}
else if (!col[i])
low[x] = min(low[x], dfn[i]);
}
if (low[x] == dfn[x]) {
int p;
++cntw;
do {
p = t.top();
t.pop();
++siz[cntw];
col[p] = cntw;
} while (p != x);
}
return;
}
inline void SPFA1(int s) {
memset(d1, -0x3f, sizeof (d1));
q.push(s);
in[s] = 1;
d1[s] = siz[s];
while (!q.empty()) {
int f = q.front();
q.pop();
in[f] = 0;
for (auto i : g1[f]) {
if (d1[i] < d1[f] + siz[i]) {
d1[i] = d1[f] + siz[i];
if (!in[i])
in[i] = 1, q.push(i);
}
}
}
return;
}
inline void SPFA2(int s) {
memset(d2, -0x3f, sizeof (d2));
q.push(s);
in[s] = 1;
d2[s] = 0;
while (!q.empty()) {
int f = q.front();
q.pop();
in[f] = 0;
for (auto i : g2[f]) {
if (d2[i] < d2[f] + siz[i]) {
d2[i] = d2[f] + siz[i];
if (!in[i])
in[i] = 1, q.push(i);
}
}
}
return;
}
inline void add(int x, int y) {
g[x].push_back(y);
return;
}
inline void _add(int x, int y) {
g1[x].push_back(y);
return;
}
inline void add_(int x, int y) {
g2[x].push_back(y);
return;
}
int main() {
read(n), read(m);
while (m--) {
read(x), read(y);
add(x, y);
}
for (int i = 1; i <= n; ++i) {
if (dfn[i] == 0)
DFS(i);
}
for (int i = 1; i <= n; ++i) {
for (auto j : g[i]) {
if (col[i] == col[j])
continue;
if (u.find(mkp(col[i], col[j]))
!= u.end())
continue;
u.insert(mkp(col[i], col[j]));
_add(col[i], col[j]);
add_(col[j], col[i]);
}
}
SPFA1(col[1]), SPFA2(col[1]);
res = siz[col[1]];
for (int i = 1; i <= cntw; ++i) {
for (auto j : g1[i])
res = max(res, d1[j] + d2[i]);
}
print(res);
return 0;
}
} // namespace XSC062
G. Tourist Reform
http://222.180.160.110:1024/contest/3698/problem/7
想到一半想不下去了,想着看看 tj 前两排拿点思路吧,结果 tj 前两排就是我已经想到的所有内容。于是我又往下看了一排,好家伙,直接把分化点给我摆出来了……
先尝试求解出最小 \(r_i\) 的值。
我们都知道,边双在执行有向化操作(暂且这么称呼)后一定可以变成强连通分量。
把原图按边双缩点之后会成为一棵树,我们需要把树上的所有边有向化以达到最大收益。
这里有一个分化点,as we all know,树有 \(n\) 个点和 \(n - 1\) 条边。我们可以选取 \(n - 1\) 个点每个各自占领一条边并发向其他点,也就是说,一定会有一个点无法发向其他点,那么这个点的 \(r\) 值就是它自己的 siz
。
因为每个点的 \(r\) 值最小都是自己的 siz
,除非在其他 \(n - 1\) 个点当中存在更小者,否则这个点的 siz
就是最小 \(r\) 值。
如果我们让其他 \(n - 1\) 个点全部连向该点(令该点为树根,图中所有边从儿子指向父亲即可),那么它们的 \(r\) 值将至少为该点的 siz
与其自身的 siz
之和,总之必定会比该点的 siz
大。
在这种处理模式下,全图最小 \(r\) 值即为该点 \(r\) 值。那很简单,我们让 siz
最大的点成为这个点就好了。
接下来考虑具体连边方式。
边双内部,我们按照 DFS 顺序即可。
理由是,我们知道只要不包含任何入度为 0 或出度为 0 的点的连通图就是强连通的,而对于 DFS 来说,搜出来的边双首先是连通的,其次出度为 0 的点由于没有返祖所以不可能存在,入度为 0 的点会被算作另一个边双所以也不存在。DFS 一定会遍历到每条边(没过到的是反向边,其正方向被遍历过),所以根据 DFS 搜索路径连边即可。
缩点后,按照刚刚介绍的方式,令 siz
最大者为根,儿子向父亲连边即可。
—— · EOF · ——
真的什么也不剩啦 😖