【知识】Tarjan 算法
Tarjan
P8435 【模板】点双连通分量
#include <bits/stdc++.h>
using namespace std;
#define FOR(i, a, b) for (int i = (a); i <= (b); ++i)
#define ROF(i, a, b) for (int i = (a); i >= (b); --i)
#define DEBUG(x) cerr << #x << " = " << x << endl
#define ll long long
typedef pair <int, int> PII;
typedef unsigned int uint;
typedef unsigned long long ull;
#define i128 __int128
#define fi first
#define se second
mt19937 rnd(chrono::system_clock::now().time_since_epoch().count());
#define ClockA clock_t start, end; start = clock()
#define ClockB end = clock(); cerr << "time = " << double(end - start) / CLOCKS_PER_SEC << "s" << endl;
//#define int long long
inline int rd(){
int x = 0, f = 1;
char ch = getchar();
while (ch < '0' || ch > '9'){
if (ch == '-')
f = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9')
x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
return x * f;
}
#define rd rd()
void wt(int x){
if (x < 0)
putchar('-'), x = -x;
if (x > 9)
wt(x / 10);
putchar(x % 10 + '0');
return;
}
void wt(char x){
putchar(x);
}
void wt(int x, char k){
wt(x),putchar(k);
}
namespace Star_F{
const int N = 500005, M = 4000005;
int h[N], e[M], ne[M], idx;
int s[N], cnt, top, bcc, low[N], dfn[N];
int n, m;
vector<int> ans[N];
void add(int u,int v){
e[++idx] = v, ne[idx] = h[u], h[u] = idx;
}
void tarjan(int u,int fa){
int son = 0;
low[u] = dfn[u] = ++cnt;
s[++top] = u;
for (int i = h[u]; i;i=ne[i]){
int v = e[i];
if(!dfn[v]){
son++;
tarjan(v, u);
low[u] = min(low[u], low[v]);
if(low[v]>=dfn[u]){
bcc++;
while(s[top+1]!=v)
ans[bcc].push_back(s[top--]);
ans[bcc].push_back(u);
}
}
else if(v!=fa)
low[u] = min(low[u], dfn[v]);
}
if(fa==0&&son==0)
ans[++bcc].push_back(u);
}
void Main(){
cin >> n >> m;
for (int i=1; i <= m;i++){
int u, v;
cin >> u >> v;
add(u, v), add(v, u);
}
for (int i = 1; i <= n;i++){
if(!dfn[i])
top = 0, tarjan(i, 0);
}
cout << bcc << endl;
for (int i = 1; i <= bcc;i++){
cout << ans[i].size() << " ";
for(int j:ans[i])
cout << j << " ";
cout << endl;
}
}
}
signed main(){
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
ClockA;
int T=1;
// T=rd;
while(T--) Star_F::Main();
// ClockB;
return 0;
}
/*
* ▀▀▀██████▄▄▄ _______________
* ▄▄▄▄▄ █████████▄ / \
* ▀▀▀▀█████▌ ▀▐▄ ▀▐█ | Code has no BUG! |
* ▀▀█████▄▄ ▀██████▄██ | _________________/
* ▀▄▄▄▄▄ ▀▀█▄▀█════█▀ |/
* ▀▀▀▄ ▀▀███ ▀ ▄▄
* ▄███▀▀██▄████████▄ ▄▀▀▀▀▀▀█▌ ______________________________
* ██▀▄▄▄██▀▄███▀ ▀▀████ ▄██ █ \\
* ▄▀▀▀▄██▄▀▀▌████▒▒▒▒▒▒███ ▌▄▄▀▀▀▀█_____________________________ //
* ▌ ▐▀████▐███▒▒▒▒▒▐██▌
* ▀▄▄▄▄▀ ▀▀████▒▒▒▒▄██▀
* ▀▀█████████▀
* ▄▄██▀██████▀█
* ▄██▀ ▀▀▀ █
* ▄█ ▐▌
* ▄▄▄▄█▌ ▀█▄▄▄▄▀▀▄
* ▌ ▐ ▀▀▄▄▄▀
* ▀▀▄▄▀ ██
* \ ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ ▀
* \- ▌ Name: Star_F ▀ ▀
* - ▌ (o) ▀
* /- ▌ Go Go Go ! ▀ ▀
* / ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ ▀
*/
P8436 【模板】边双连通分量
#include <iostream>
#include <cstring>
#include <vector>
using namespace std;
const int N = 500010, M = 2000010 * 2;
int n, m;
int h[N], e[M], ne[M], idx;
int dfn[N], low[N], timestamp;
int stk[N], top;
int id[N], dcc_cnt;
bool is_bridge[M];
int tot[N];
vector <int> ans[N];
void add(int a, int b)
{
e[idx] = b;
ne[idx] = h[a];
h[a] = idx ++ ;
return;
}
void tarjan(int u, int from)
{
dfn[u] = low[u] = ++ timestamp;
stk[ ++ top] = u;
for (int i = h[u]; ~i; i = ne[i])
{
int j = e[i];
if (!dfn[j])
{
tarjan(j, i);
low[u] = min(low[u], low[j]);
if (low[j] > dfn[u])
is_bridge[i] = is_bridge[i ^ 1] = true;
}
else if (i != (1 ^ from))
low[u] = min(low[u], dfn[j]);
}
if (dfn[u] == low[u])
{
dcc_cnt ++ ;
int y;
do
{
y = stk[top -- ];
id[y] = dcc_cnt;
} while (y != u);
}
return;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cin >> n >> m;
memset(h, -1, sizeof h);
for (int i = 1; i <= m; i ++ )
{
int a, b;
cin >> a >> b;
add(a, b);
add(b, a);
}
for (int i = 1; i <= n; i ++ )
if (!dfn[i]) tarjan(i, -1);
cout << dcc_cnt << endl;
for (int i = 1; i <= n; i ++ )
{
tot[id[i]] ++ ;
ans[id[i]].push_back(i);
}
int p = 1;
while (tot[p])
{
cout << tot[p] << ' ';
for (int i = 0; i < ans[p].size(); i ++ )
cout << ans[p][i] << ' ';
p ++ ;
cout << endl;
}
return 0;
}
P3388 【模板】割点(割顶)
#include <bits/stdc++.h>
using namespace std;
constexpr int N = 1e5 + 5;
int n, m, num,root;
int dn, dfn[N], low[N], cnt, buc[N],sum;
vector<int> e[N];
void Tarjan(int x){
dfn[x]=low[x]=++num;
int flag=0;
for(int i=0;i<e[x].size();i++){
int to=e[x][i];
if(!dfn[to]){
Tarjan(to);
low[x]=min(low[x],low[to]);
if(low[to]>=dfn[x]){
flag++;
if(x!=root||(flag>=2)){
buc[x]=1;
}
}
}
else low[x]=min(low[x],dfn[to]);
}
}
int main() {
cin >> n >> m;
for(int i = 1; i <= m; i++) {
int u, v;
cin >> u >> v;
e[u].push_back(v);
e[v].push_back(u);
}
for(int i=1;i<=n;i++){
if(!dfn[i]){
root=i;
Tarjan(i);
}
}
for(int i=1;i<=n;i++){
if(buc[i]) sum++;
}
cout<<sum<<endl;
for(int i=1;i<=n;i++){
if(buc[i]) cout<<i<<" ";
}
return 0;
}
P3469 [POI 2008] BLO-Blockade
对于每个点,考虑将其删除对图会产生什么影响
-
不是割点:答案就是
。 -
是割点:删除该割点产生
个联通块,则答案就是
考虑优化,显然
所以答案就是
#include <bits/stdc++.h>
using namespace std;
#define FOR(i, a, b) for (int i = (a); i <= (b); ++i)
#define ROF(i, a, b) for (int i = (a); i >= (b); --i)
#define DEBUG(x) cerr << #x << '=' << x << endl
typedef pair<int, int> PII;
typedef unsigned int uint;
typedef unsigned long long ull;
#define i128 __int128
#define fi first
#define se second
mt19937 rnd(chrono::system_clock::now().time_since_epoch().count());
#define ClockA clock_t start, end; start = clock()
#define ClockB end = clock(); cerr << "time = " << double(end - start) / CLOCKS_PER_SEC << "s" << endl;
#define int long long
inline int rd() {
int x = 0, f = 1;
char ch = getchar();
while (ch < '0' || ch > '9') {
if (ch == '-') f = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9') x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
return x * f;
}
#define rd rd()
void wt(int x) {
if (x < 0) putchar('-'), x = -x;
if (x > 9) wt(x / 10);
putchar(x % 10 + '0');
return;
}
void wt(char x) {
putchar(x);
}
void wt(int x, char k) {
wt(x), putchar(k);
}
namespace Star_F {
const int N = 100005, M = 500005;
int n, m, h[N], dfn[N], low[N];
int sz[N], ans[N];
struct Edge {
int nxt, to;
};
Edge E[M << 1];
int tot;
void add(int u, int v) {
E[++tot].nxt = h[u];
E[tot].to = v;
h[u] = tot;
}
int cnt;
void tarjan(int u) {
dfn[u] = low[u] = ++cnt;
int sum = 0;
sz[u] = 1;
for (int i = h[u]; i; i = E[i].nxt) {
int v = E[i].to;
if (!dfn[v]) {
tarjan(v);
sz[u] += sz[v];
if (low[v] >= dfn[u]) {
ans[u] += sz[v] * sum;
sum += sz[v];
}
low[u] = min(low[u], low[v]);
} else {
low[u] = min(low[u], dfn[v]);
}
}
ans[u] += (n - sum - 1) * sum;
ans[u] += n - 1;
}
void Main() {
n = rd, m = rd;
FOR(i, 1, m) {
int u = rd, v = rd;
add(u, v);
add(v, u);
}
tarjan(1);
FOR(i, 1, n) {
wt(ans[i] * 2,'\n');
}
}
}
signed main() {
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
ClockA;
int T = 1;
// T = rd;
while (T--) Star_F::Main();
// ClockB;
return 0;
}
/*
* ▀▀▀██████▄▄▄ _______________
* ▄▄▄▄▄ █████████▄ / \
* ▀▀▀▀█████▌ ▀▐▄ ▀▐█ | Code has no BUG! |
* ▀▀█████▄▄ ▀██████▄██ | _________________/
* ▀▄▄▄▄▄ ▀▀█▄▀█════█▀ |/
* ▀▀▀▄ ▀▀███ ▀ ▄▄
* ▄███▀▀██▄████████▄ ▄▀▀▀▀▀▀█▌ ______________________________
* ██▀▄▄▄██▀▄███▀ ▀▀████ ▄██ █ \\
* ▄▀▀▀▄██▄▀▀▌████▒▒▒▒▒▒███ ▌▄▄▀▀▀▀█_____________________________ //
* ▌ ▐▀████▐███▒▒▒▒▒▐██▌
* ▀▄▄▄▄▀ ▀▀████▒▒▒▒▄██▀
* ▀▀█████████▀
* ▄▄██▀██████▀█
* ▄██▀ ▀▀▀ █
* ▄█ ▐▌
* ▄▄▄▄█▌ ▀█▄▄▄▄▀▀▄
* ▌ ▐ ▀▀▄▄▄▀
* ▀▀▄▄▀ ██
* \ ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ ▀
* \- ▌ Name: Star_F ▀ ▀
* - ▌ (o) ▀
* /- ▌ Go Go Go ! ▀ ▀
* / ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ ▀
*/
P2860 [USACO06JAN] Redundant Paths G
注意到答案为缩点后叶子节点的个数
由于是边双,一条边
#include <bits/stdc++.h>
using namespace std;
const int N = 5005, M = 100005;
int n, m, vis[N << 1], du[N], ans;
int h[N], e[N], ne[N], idx=1;
int u[M], v[M], id[N];
int dfn[N], low[N], s[N], col, top, now;
void add(int u,int v){
e[++idx] = v, ne[idx] = h[u], h[u] = idx;
}
void tarjan(int u){
dfn[u] = low[u] = ++now;
s[++top] = u;
for (int i = h[u]; i; i=ne[i]){
int v = e[i];
if(!vis[i]){
vis[i] = vis[i ^ 1] = 1;
if(!dfn[v]){
tarjan(v);
low[u] = min(low[u], low[v]);
}
else
low[u] = min(low[u], dfn[v]);
}
}
if(low[u]==dfn[u]){
id[u] = ++col;
while(s[top]!=u)
id[s[top--]] = col;
top--;
}
}
int main(){
cin >> n >> m;
for (int i = 1; i <= m;i++){
cin >> u[i] >> v[i];
add(u[i], v[i]), add(v[i], u[i]);
}
for (int i = 1; i <= n;i++){
if(!dfn[i])
tarjan(i);
}
for (int i = 1; i <= m;i++){
if(id[u[i]]!=id[v[i]])
du[id[u[i]]]++, du[id[v[i]]]++;
}
for (int i = 1; i <= col;i++)
ans += du[i] == 1;
cout << (ans + 1 >> 1) << endl;
return 0;
}
P5058 [ZJOI2004] 嗅探器
题意:从
其实就是板子题,只需要再 Tarjan 中加上 low[y] >= dfn[x] && dfn[b] >= dfn[y]
即可。
#include <bits/stdc++.h>
using namespace std;
int n, m, a, b;
const int N = 210000;
vector<int> v[N];
int dfn[N], low[N], step, fa[N], son[N], cut[N];
void tarjan(int x) {
dfn[x] = low[x] = ++step;
for (int i = 0; i < v[x].size(); i++) {
int y = v[x][i];
if (y == fa[x]) continue;
if (!dfn[y]) {
fa[y] = x;
son[x]++;
tarjan(y);
if (low[y] >= dfn[x] && dfn[b] >= dfn[y])
cut[x] = 1;
low[x] = min(low[x], low[y]);
} else
low[x] = min(low[x], dfn[y]);
}
if (son[x] == 1 && fa[x] == 0)
cut[x] = 0;
}
int main() {
cin >> n;
while (1) {
int x, y;
cin >> x >> y;
if (!(x + y))
break;
v[x].push_back(y);
v[y].push_back(x);
}
cin >> a >> b;
tarjan(a);
for (int i = 1; i <= n; i++) {
if (i == a)
continue;
if (cut[i])
cout<<i, exit(0);
}
cout << "No solution" << endl;
}
P3225 [HNOI2012] 矿场搭建
首先先缩点,然后分类讨论。
-
没有割点的块,所以需要在块内两个不同节点上建两个出口(任意一个塌了可以用另外一个)
还要特判一下:如果只有一个点的话建一个出口就行。
-
有一个割点的块,这种块要在除割点外任意一点建一个出口,这样如果割点塌了,可以用那个出口,如果出口塌了可以通过割点去到别的块。
-
有两个或以上割点的块,这种块是不需要出口的,不难发现,不管哪个点塌了,都可以通过没塌的割点去到别的块。
#include <cstring>
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
typedef unsigned long long ULL;
const int N = 1010, M = 1010;
int n, m;
int h[N], e[M], ne[M], idx;
int dfn[N], low[N], timestamp;
int stk[N], top;
int dcc_cnt;
vector<int> dcc[N];
bool cut[N];
int root;
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}
void tarjan(int u)
{
dfn[u] = low[u] = ++ timestamp;
stk[ ++ top] = u;
if (u == root && h[u] == -1)
{
dcc_cnt ++ ;
dcc[dcc_cnt].push_back(u);
return;
}
int cnt = 0;
for (int i = h[u]; ~i; i = ne[i])
{
int j = e[i];
if (!dfn[j])
{
tarjan(j);
low[u] = min(low[u], low[j]);
if (dfn[u] <= low[j])
{
cnt ++ ;
if (u != root || cnt > 1) cut[u] = true;
++ dcc_cnt;
int y;
do {
y = stk[top -- ];
dcc[dcc_cnt].push_back(y);
} while (y != j);
dcc[dcc_cnt].push_back(u);
}
}
else low[u] = min(low[u], dfn[j]);
}
}
int main()
{
int T = 1;
while (cin >> m, m)
{
for (int i = 1; i <= dcc_cnt; i ++ ) dcc[i].clear();
idx = n = timestamp = top = dcc_cnt = 0;
memset(h, -1, sizeof h);
memset(dfn, 0, sizeof dfn);
memset(cut, 0, sizeof cut);
while (m -- )
{
int a, b;
cin >> a >> b;
n = max(n, a), n = max(n, b);
add(a, b), add(b, a);
}
for (root = 1; root <= n; root ++ )
if (!dfn[root])
tarjan(root);
int res = 0;
ULL num = 1;
for (int i = 1; i <= dcc_cnt; i ++ )
{
int cnt = 0;
for (int j = 0; j < dcc[i].size(); j ++ )
if (cut[dcc[i][j]])
cnt ++ ;
if (cnt == 0)
{
if (dcc[i].size() > 1) res += 2, num *= dcc[i].size() * (dcc[i].size() - 1) / 2;
else res ++ ;
}
else if (cnt == 1) res ++, num *= dcc[i].size() - 1;
}
printf("Case %d: %d %llu\n", T ++, res, num);
}
return 0;
}
CF51F Caterpillar
[Alex_Wei](CF51F Caterpillar - 洛谷专栏)
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
· 凌晨三点救火实录:Java内存泄漏的七个神坑,你至少踩过三个!