【YBTOJ】【Luogu P2272】[ZJOI2007]最大半连通子图
链接:
题目大意:
一个半连通图 \(G=(V,E)\) 中 \(\forall u,v\in V\) 满足 \(u\rightarrow v\) 或 \(v\rightarrow u\)。
给定一个图,求出它最大半连通子图及其个数。
正文:
用 Tarjan 对强连通分量缩点,问题就转化成了求新图中最长链及其个数。然后就可以用拓扑 DP 求解最长距离和方案数,设 \(f_i\) 表示以 \(i\) 为终点的方案数,\(dis_i\) 表示以 \(i\) 为终点的最长链。
不过在此之前,为了 DP 的正确性,需要对新图中的重边删去。可以在转移的过程中用数组 \(used(i)\) 表示来到 \(i\) 点时的边的起点。这样记录即可。
代码:
const int N = 1e5 + 10;
int n, m;
ll mod;
int head[N], tot;
struct edge
{
int from, to, nxt;
}e[N * 12];
void add(int u, int v)
{
e[++tot] = (edge){u, v, head[u]}, head[u] = tot;
}
int dfn[N], low[N], stack[N], color[N], cnt[N], top, num, t;
void Tarjan(int u)
{
dfn[u] = low[u] = ++num;
stack[++top] = u;
for (int i = head[u]; i; i = e[i].nxt)
{
int v = e[i].to;
if (!dfn[v])
{
Tarjan(v);
low[u] = min(low[u], low[v]);
}
else
if(!color[v])
low[u] = min(low[u], dfn[v]);
}
if(low[u] == dfn[u])
{
t++;
do
{
color[stack[top]] = t;
cnt[t]++;
top--;
}while(u != stack[top + 1]);
}
}
bool cmp (edge a, edge b)
{
if (a.from == b.from) return a.nxt < b.nxt;
return a.from < b.from;
}
int ans;
ll f[N], siz[N], ind[N], dis[N], used[N];
queue <int> q;
void Topo()
{
for (int i = 1; i <= t; i++)
if (!ind[i])
{
q.push(i);
dis[i] = cnt[i];
f[i] = 1;
if (dis[ans] < dis[i]) ans = i;
}
while (!q.empty())
{
int u = q.front(); q.pop();
for (int i = head[u]; i; i = e[i].nxt)
{
int v = e[i].to;
--ind[v];
if (!ind[v]) q.push(v);
if (used[v] == u) continue;
used[v] = u;
if (dis[v] < dis[u] + cnt[v])
{
dis[v] = dis[u] + cnt[v];
f[v] = 0;
if (dis[ans] < dis[v]) ans = v;
}
if (dis[v] == dis[u] + cnt[v])
f[v] = (f[v] + f[u]) % mod;
}
}
}
int main()
{
// freopen(".in", "r", stdin);
// freopen(".out", "w", stdout);
scanf ("%d%d%lld", &n, &m, &mod);
for (int i = 1, u, v; i <= m; ++i)
{
scanf ("%d%d", &u, &v);
add(u, v);
}
for (int i = 1; i <= n; i++)
if(!dfn[i]) Tarjan(i);
// Start Removing
tot = 0;
memset (head, 0, sizeof head);
for (int i = 1; i <= m; i++)
add(color[e[i].from], color[e[i].to]);
sort (e + 1, e + 1 + m, cmp);
int m_ = m;
for (int i = 1, j = 1; i <= m; i++)
if (!(e[i].from == e[i].to) && (e[i].from != e[i - 1].from || e[i].to != e[i - 1].to))
e[j++] = e[i];
else m_--;
m = m_;
tot = 0;
memset (head, 0, sizeof head);
for (int i = 1; i <= m; i++)
add(e[i].from, e[i].to), ind[e[i].to]++;
//---
Topo();
ll Ans = 0;
for (int i = 1; i <= t; i++)
if(dis[i] == dis[ans]) Ans = (Ans + f[i]) % mod;
printf ("%d\n%lld", dis[ans], Ans);
return 0;
}