【ybtoj 高效进阶 3.4】 【强连通分量】 最大半联通子图
题目
解题思路
强连通分量肯定也是半联通子图
可以先跑一遍强连通分量
然后再跑一遍DP求出最大半联通子图和方案数
注意不能建重边,不然会重复累计方案数
代码
#include<algorithm>
#include<iostream>
#include<cstdio>
#include<queue>
using namespace std;
queue<int>q;
struct lzf{
int x,to,next;
}f[2000100],f2[2000100];
long long cnt,ans[200010];
int n,m,t,z,x,y,maxn,mo,tot;
int sk[200010],ru[200010],ins[200010],low[200010];
int head[200010],head2[200010],dfn[200010],sum[200010],dis[200010];
void add(int x,int y)
{
f[++t].to=y;
f[t].x=x;
f[t].next=head[x];
head[x]=t;
}
void add2(int x,int y)
{
f2[++t].to=y;
f2[t].next=head2[x];
head2[x]=t;
}
bool cmp(lzf f,lzf y)
{
if (ins[f.x]!=ins[y.x])
return ins[f.x]<ins[y.x];
else return ins[f.to]<ins[y.to];
}
void tarjan(int x)
{
dfn[x]=low[x]=++tot;
sk[++t]=x;
for (int i=head[x];i;i=f[i].next)
if (!dfn[f[i].to])
{
tarjan(f[i].to);
low[x]=min(low[x],low[f[i].to]);
}
else if (!ins[f[i].to]) low[x]=min(low[x],low[f[i].to]);
if (dfn[x]==low[x])
{
ins[x]=++z;
sum[z]++;
while (sk[t]!=x)
{
sum[z]++;
ins[sk[t--]]=z;
}
t--;
}
}
int main()
{
scanf("%d%d%d",&n,&m,&mo);
for (int i=1;i<=m;i++)
{
scanf("%d%d",&x,&y);
add(x,y);
}
t=0;
for (int i=1;i<=n;i++)
if (!dfn[i]) tarjan(i); //求出强连通分量
sort(f+1,f+m+1,cmp); //根据边的两点排序,去重
t=0;
for (int i=1;i<=m;i++)
if ((ins[f[i].x]!=ins[f[i].to])&&(ins[f[i].x]!=ins[f[i-1].x]||ins[f[i].to]!=ins[f[i-1].to]))
add2(ins[f[i].x],ins[f[i].to]),ru[ins[f[i].to]]++; //避免建重边
for (int i=1;i<=z;i++)
{
ans[i]=1;
dis[i]=sum[i];
maxn=max(dis[i],maxn);
if (!ru[i]) q.push(i);
}
while (!q.empty())
{
int k=q.front();
q.pop();
for (int j=head2[k];j;j=f2[j].next)
{
int l=f2[j].to;
ru[l]--;
if (dis[l]<dis[k]+sum[l])
{
dis[l]=dis[k]+sum[l];
maxn=max(maxn,dis[l]);
ans[l]=ans[k];
}
else if (dis[l]==dis[k]+sum[l])
ans[l]=(ans[l]+ans[k])%mo;
if (!ru[l]) q.push(l);
} //求出最大边权和以及方案数
}
for (int i=1;i<=z;i++)
if (dis[i]==maxn) cnt=(cnt+ans[i])%mo; //累计最大边权和的方案数
printf("%d\n%lld",maxn,cnt);
}