洛谷P2272 [ZJOI2007]最大半连通子图 题解
题意:
一个有向图 $G=\left(V,E\right)$ 称为半连通的,如果满足:$\forall u,v\in V$,满足 $u\to v$ 或 $v\to u$,即对于图中任意两点 $u,v$,存在一条 $u$ 到 $v$ 的有向路径或者从 $v$ 到 $u$ 的有向路径。
若 $G’=\left(V’,E’\right)$ 满足 $V’\subseteq V$,$E’$ 是 $E$ 中所有跟 $V’$ 有关的边,则称 $G’$ 是 $G$ 的一个导出子图。若 $G’$ 是 $G$ 的导出子图,且 $G’$ 半连通,则称 $G’$ 为 $G$ 的半连通子图。若 $G’$ 是 $G$ 所有半连通子图中包含节点数最多的,则称 $G’$ 是 $G$ 的最大半连通子图。
给定一个有向图 $G$,请求出 $G$ 的最大半连通子图拥有的节点数 $K$,以及不同的最大半连通子图的数目 $C$。由于 $C$ 可能比较大,仅要求输出 $C$ 对 $X$ 的余数。
输入格式:
第一行包含两个整数 $N,M,X$。$N,M$分别表示图 $G$ 的点数与边数,$X$ 的意义如上文所述。
接下来 $M$ 行,每行两个正整数 $a,b$,表示一条有向边 $\left(a,b\right)$。图中的每个点将编号为 $1,2,3\dots N$,保证输入中同一个$\left(a,b\right)$不会出现两次。
输出格式:
应包含两行,第一行包含一个整数 $K$,第二行包含整数 $C\bmod X$。
数据范围:
对于 $100\%$ 的数据,$N\le 10^5$,$M\le 10^6$,$X\le 10^8$。
通俗地说,$G$ 的一个导出子图 $G^{\prime} = (V^{\prime},E^{\prime})$ 是半连通子图,则有
对于某一个强连通分量,如果有某个点在某个半连通子图中,
则这个强连通分量中的所有点都可以加入,并且显然,满足半连通的性质
因此我们可以先强连通分量缩点一下。
考虑一个DAG(有向无环图),它的半连通子图一定以拓扑序连通
考虑dp。设 $f_i$ 表示(缩点后)到点 $i$ 为止的(带权)最大半连通子图的大小。
设连接到点 $i$ 的集合为 $N^{+}(i)$ ,记点 $i$ 对应的强连通分量大小 $s_i$ 为 $i$ 的点权,则
最大的 $f_i$ 就是第一问的答案。
第二问的话,类似于 P2047 [NOI2007] 社交网络 ,在更新 $f_i$ 的时候维护一个 $g_i$ 就行了
具体地,在 topo dp 的过程中
当 $f_u + s_i > f_v$ ,显然 $u$ 可以更新 $v$ 的答案,则 $f_v \gets f_u +s_i,~g_v \gets g_u$
当 $f_v = f_u + s_i$ 时,直接 $g_v \gets g_v + g_u$ 就好了。
当然,事实上我们并不需要跑一遍 topo 排序
因为 Tarjan 缩点后的点的排列顺序是逆拓扑序
逆拓扑序可以考虑 Tarjan 的过程,其实把每个SCC弹出的时候相当于就是在做逆拓扑了
也就是说,我们只要把缩点后的点倒着扫一遍dp就好了
同时,因为这题是计数题,所以我们要判重边,可以用这个来判断
if(used[v] == u) continue;
used[v] = u;
// dp ....
时间复杂度 $\mathcal{O}(n + m)$
代码:
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f
#define N ((int)(1e5+15))
#define M ((int)(1e6+15))
bitset<N> ins;
int f[N],g[N],used[N];
int n,m,mod,pos=1,head[N];
int stktop,tim,dfncnt,scnt;
int sz[N],dfn[N],low[N],stk[N],scc[N],top[N];
struct Edge{int u,v,next;} e[M];
void down(int &x,int y) { x > y ? x = y : 0; }
void up(int &x,int y) { x < y ? x = y : 0; }
void add(int &x,int y) { (x += y) >= mod ? x -= mod : 0; }
void addEdge(int u,int v) { e[++pos]={u,v,head[u]}; head[u] = pos; }
void tarjan(int u)
{
dfn[u] = low[u] = ++tim;
ins[u]=1; stk[++stktop]=u;
for(int i=head[u]; i; i=e[i].next)
{
int v=e[i].v;
if(!dfn[v])
{
tarjan(v); down(low[u], low[v]);
}else if(ins[v]) down(low[u], dfn[v]);
}
if(dfn[u] == low[u])
{
top[++scnt] = u;
for(int p=0; p!=u; )
{
p = stk[stktop--]; ins[p]=0;
scc[p]=scnt; ++sz[scnt];
}
}
}
void solve()
{
for(int u=scnt; u; u--)
{
if(!f[u]) {f[u] = sz[u]; g[u] = 1;}
for(int i=head[u]; i; i=e[i].next)
{
int v=e[i].v;
if(used[v] == u) continue;
used[v] = u;
if(f[u] + sz[v] > f[v])
{
f[v] = f[u] + sz[v]; g[v] = g[u];
}else if(f[u] + sz[v] == f[v])
{ add(g[v], g[u]); }
}
}
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
// freopen("check.in","r",stdin);
// freopen("check.out","w",stdout);
cin >> n >> m >> mod;
for(int i=1,u,v; i<=m; i++) { cin >> u >> v; addEdge(u,v); }
for(int i=1; i<=n; i++) if(!dfn[i]) tarjan(i);
pos=1; for(int i=1; i<=n; i++) head[i] = 0;
for(int i=2; i<=m+1; i++)
{
int u = scc[e[i].u], v = scc[e[i].v];
if(u != v) addEdge(u,v);
}
solve();
int ans1=0,ans2=0;
for(int i=1; i<=scnt; i++) up(ans1, f[i]);
for(int i=1; i<=scnt; i++) if(f[i] == ans1) add(ans2, g[i]);
cout << ans1 << '\n' << ans2 << '\n';
return 0;
}
这码风变成这样全都赖 Roundgod 和 yhx(雾
参考文献:
[1] https://yhx-12243.github.io/OI-transit/records/lydsy1093%3Blg2272.html