P2272 [ZJOI2007] 最大半连通子图 解题报告
题意
在有向图 \(G\) 中找出节点数最多的半连通导出子图(称为最大半连通子图),并输出其节点数 \(K\) 以及不同最大半连通子图的个数 \(C\)
半连通的定义:对于图中任意两点 \(u\),\(v\),至少存在一条从 \(u\) 到 \(v\) 或从 \(v\) 到 \(u\) 的有向路径。
思路
这个性质等价于将原图缩点后得到的 DAG必须是一条链(即任意两点之间都有路径)。
因为如果 DAG 中存在分叉(例如两个不同分支没有相互可达路径),那么这两个分支中的点就不满足半连通条件。
因此,最大半连通子图对应于缩点后 DAG 上的一条最长路径
且该路径上的所有分量必须全部选取,因为选取部分分量会破坏半连通性,且加入剩余点不会破坏半连通性。
不同的最大半连通子图对应不同的最长路径。
注意事项
- 要处理重边,不同边的统计会导致同一个最长路径被重复计数多次。
代码
#include<bits/stdc++.h>
using namespace std;
#define inf 0x3f
#define int long long
const int N=1e5+10;
map<pair<int,int>,bool>num;
int n,m,low[N],dfn[N],vis[N],a[N],b[N],dp1[N],s[N],t,cnt,sum[N],ans1;
long long dp2[N],ans2,mod;
vector<int>q[N],p[N];
void dfs(int x)
{
dfn[x]=low[x]=++t;
s[t]=x;
for(int i=0;i<q[x].size();i++)
{
int v=q[x][i];
if(!dfn[v])
{
dfs(v);
low[x]=min(low[x],low[v]);
}
else if(!vis[v]) // 在栈中
{
low[x]=min(low[x],dfn[v]);
}
}
if(dfn[x]==low[x])
{
cnt++;
while(s[t]!=x)
{
vis[s[t]]=cnt;
sum[cnt]++;
t--;
}
vis[x]=cnt;
sum[cnt]++;
t--;
}
}
signed main()
{
cin>>n>>m>>mod;
for(int i=1;i<=m;i++)
{
int x,y;
cin>>x>>y;
q[x].push_back(y);
}
for(int i=1;i<=n;i++)
if(!dfn[i] && !vis[i])
dfs(i);
t=0;
for(int i=1;i<=n;i++)
for(int j=0;j<q[i].size();j++)
if(vis[i]!=vis[q[i][j]] && !num[make_pair(vis[i],vis[q[i][j]])])
{
p[vis[i]].push_back(vis[q[i][j]]);
b[vis[q[i][j]]]++;
num[make_pair(vis[i],vis[q[i][j]])]=1;
}
for(int i=1;i<=cnt;i++)
{
if(b[i]==0)
a[++t]=i;
dp2[i]=1;
}
for(int i=1;i<=t;i++)
{
sum[a[i]]+=dp1[a[i]];
ans1=max(ans1,sum[a[i]]);
for(int j=0;j<p[a[i]].size();j++)
{
int v=p[a[i]][j];
b[v]--;
if(b[v]==0)
a[++t]=v;
if(sum[a[i]]==dp1[v])
dp2[v]=(dp2[v]+dp2[a[i]])%mod;
if(sum[a[i]]>dp1[v])
{
dp2[v]=dp2[a[i]];
dp1[v]=sum[a[i]];
}
}
}
for(int i=1;i<=cnt;i++)
{
if(sum[i]==ans1)
ans2=(ans2+dp2[i])%mod;
if(sum[i]>ans1)
{
ans2=dp2[i];
ans1=sum[i];
}
}
cout<<ans1<<"\n"<<ans2%mod;
return 0;
}

浙公网安备 33010602011771号