AcWing 1175. 最大半连通子图
题目传送门
一、题目描述
相当于是把对强连通分量的连通换成了半联通。
让你求最大半联通图中点的数目和一共有多少个最大半联通子图。
二、解题思路
经过仔细思考我们不难发现,强连通分量必定是半联通的
举个例子:
环 \(1 —> 2 — > 3 —>1\) 显然是一个强连通分量
我们在该环中不难找到一个最大半联通子图 \(1 – > 2 –> 3\) 或者 \(2 –> 3 –> 1\)
我们又知道,一个图处理完强连通分量后,如果将一个强连通分量看成一个点,那么整个图就是一个拓扑图(\(DAG\))
经过观察发现,处理完后得到的\(DAG\)图上的每一条链都可以看作是一个半联通子图
于是,第一个问题便转化成了在\(DAG\)上求最长链中点的数目
第二个问题便转化成了在\(DAG\)上求有多少条最长链,即图中最长链的数目
注意!!还没有完
在转化得到的\(DAG\)图中,每个点之间可能连了多条边,这会使我们在求有多少条最长链时出现错误(why??)!!
所以,我们需要借助\(hash\)表来判重!!
三、实现代码
#include
using namespace std;
typedef long long LL;
const int N = 100010, M = 2000010; // 因为要建新图,两倍的边
int n, m, mod; // 点数、边数、取模的数
int dfn[N], low[N], timestamp;
int stk[N], top;
bool in_stk[N];
int id[N], scc_cnt, scc_size[N];
int f[N], g[N];
int h[N], hs[N], e[M], ne[M], idx; // h: 原图;hs: 新图
void add(int h[], int a, int b) {
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
// tarjan求强连通分量
void tarjan(int u) {
dfn[u] = low[u] = ++timestamp;
stk[++top] = u, in_stk[u] = true;
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]);
} else if (in_stk[j])
low[u] = min(low[u], dfn[j]);
}
if (dfn[u] == low[u]) {
++scc_cnt;
int y;
do {
y = stk[top--];
in_stk[y] = false;
id[y] = scc_cnt;
scc_size[scc_cnt]++;
} while (y != u);
}
}
int main() {
memset(h, -1, sizeof h); //原图
memset(hs, -1, sizeof hs); //新图
cin >> n >> m >> mod;
for (int i = 0; i < m; i++) {
int a, b;
cin >> a >> b;
add(h, a, b);
}
// (1) 求SCC
for (int i = 1; i <= n; i++)
if (!dfn[i]) tarjan(i);
// (2) 缩点,建图
//对连着两个不同强连通分量的边进行判重
unordered_set S; // 判断是否为重边 (u, v) -> u * 1000000ll + v
for (int i = 1; i <= n; i++)
for (int j = h[i]; ~j; j = ne[j]) {
int k = e[j];
int a = id[i], b = id[k];
LL hash = a * 1000000ll + b; //计算DAG中两个点的hash值
if (a != b && !S.count(hash)) { //去重边
add(hs, a, b); //加入到新图
S.insert(hash); //记录a,b端点的边使用过了
}
}
// (3) 根据拓扑序遍历DAG,从scc_cnt向前遍历自然满足拓扑序
// 计算以每个“点”(强连通分量)为终点的最长链中的点的数目f[i]和数量g[i]
for (int i = scc_cnt; i; i--) {
// base case 递推起点
if (!f[i]) { // 说明是入度为0的点
f[i] = scc_size[i];
g[i] = 1;
}
for (int j = hs[i]; ~j; j = ne[j]) {
int k = e[j]; // 边(i, k)
if (f[k] < f[i] + scc_size[k]) {
f[k] = f[i] + scc_size[k];
g[k] = g[i];
} else if (f[k] == f[i] + scc_size[k]) //如果以“点”的为终点的两条长链长度相同,那么将所得的的数目相加
g[k] = (g[k] + g[i]) % mod;
}
}
// (4) 求解答案
//最后统计一下以哪个点为终点的长链的点的数目最多,将他所携带的点的数目和最长链数量揪出来
int maxf = 0, sum = 0; // 最大半连通子图节点数、对应方案数
for (int i = 1; i <= scc_cnt; i++)
if (f[i] > maxf)
maxf = f[i], sum = g[i];
else if (f[i] == maxf)
sum = (sum + g[i]) % mod;
//输出
printf("%d\n", maxf);
printf("%d\n", sum);
return 0;
}