最大半连通子图【ybt高效进阶3-4-3】【luogu P2272】
题目大意:
给你一张图,让你求它的最大半连通子图的个数和大小,个数对一个给出的数取模。 一个子图是半连通子图,就是这个子图的任意两个点之间都有路径从一个点到另一个点。(只要能从任意一边到另一边即可,注意与强连通区分)
思路:
首先建图,然后Tarjan缩点,缩完点后图就变成了一个DAG(有向无环图?),强连通分量必然也是一个半连通子图。
这时我们发现,一条链也是一个合法的答案,所以缩点之后,因为没有环了,合法的答案必定是一条链(自己好好理解)
所以我们可以搞一个一个拓扑排序DP。
设a[i]表示每个强连通分量的节点个数,dp[i]表示以点i为终点的链的最大的点数和,ans[i]表示以i为终点的最大链的个数,那么,我们有:
{
d
p
[
j
]
=
d
p
[
i
]
+
a
[
v
]
,
a
n
s
[
j
]
=
a
n
s
[
i
]
(
d
p
[
i
]
+
a
[
j
]
>
d
p
[
j
]
)
a
n
s
[
j
]
+
=
a
n
s
[
i
]
(
d
p
[
i
]
+
a
[
j
]
=
d
p
[
j
]
)
\left\{\begin{matrix} & dp[j]=dp[i]+a[v],ans[j]=ans[i]~~ (dp[i]+a[j]>dp[j])\\ & ans[j]+=ans[i]~~ (dp[i]+a[j]=dp[j]) \end{matrix}\right.
{dp[j]=dp[i]+a[v],ans[j]=ans[i] (dp[i]+a[j]>dp[j])ans[j]+=ans[i] (dp[i]+a[j]=dp[j])
其中j是i通向的连通块。
所以最终答案为:
m
a
x
(
f
[
i
]
)
max(f[i])
max(f[i])和
∑
i
=
1
t
o
t
a
n
s
[
i
]
(
f
[
i
]
=
=
m
a
x
(
f
[
i
]
)
)
(
1
<
=
i
<
=
t
o
t
)
\sum_{i=1}^{tot}ans[i](f[i]==max(f[i]))~~~(1<=i<=tot)
∑i=1totans[i](f[i]==max(f[i])) (1<=i<=tot)
注意,建图时要判重边,否则会多统计
代码:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<stack>
#include<queue>
#include<vector>
#define r register
#define rep(i,x,y) for(r int i=x;i<=y;++i)
#define per(i,x,y) for(r int i=x;i>=y;--i)
using namespace std;
typedef long long ll;
const int V=(1e6+10)*9;
ll pos,n,m,mod,x,y,now,du[V/10],cnt[V/10];
ll top,num,tot,head1[V/10],head2[V/10];
ll in[V/10],low[V/10],dfn[V/10];
ll dp[V/10],ans[V/10],res,tj;
struct node
{
ll nxt,to;
}e1[V],e2[V];
struct edge
{
ll x,y;
}a[V/10];
queue<ll> q;
stack<ll> s;
void add1(ll x,ll y)
{
e1[++top]=(node){head1[x],y};
head1[x]=top;
}
void add2(ll x,ll y)
{
e2[++tot]=(node){head2[x],y};
head2[x]=tot;
}
void tarjan(ll x) //tarjan缩点
{
dfn[x]=low[x]=++pos;
s.push(x);
for(r ll i=head1[x];i;i=e1[i].nxt)
{
ll y=e1[i].to;
if(!dfn[y])
{
tarjan(y);
low[x]=min(low[x],low[y]);
}
else if(!in[y]) low[x]=min(low[x],low[y]);
}
if(dfn[x]==low[x])
{
in[x]=++now;
cnt[now]=1;
while(s.top()!=x)
{
ll y=s.top();
++cnt[now];
in[y]=now;
s.pop();
}
s.pop();
}
}
bool cmp(edge x,edge y) //去重排序的函数
{
if(x.x==y.x) return x.y<y.y;
return x.x<y.x;
}
void topsort()
{
rep(i,1,n)
if(!du[i])
{
q.push(i);
dp[i]=cnt[i];
res=max(dp[i],res);
ans[i]=1;
}
while(!q.empty() )
{
ll x=q.front() ;
q.pop();
for(r ll i=head2[x];i;i=e2[i].nxt )
{
ll y=e2[i].to;
--du[y];
if(!du[y]) q.push(y);
if(dp[y]<dp[x]+cnt[y])//状态转移
{
dp[y]=dp[x]+cnt[y];
ans[y]=ans[x];
}
else if(dp[y]==dp[x]+cnt[y])
{
ans[y]+=ans[x];
ans[y]%=mod;
}
res=max(res,dp[y]);
}
}
}
int main()
{
scanf("%lld%lld%lld",&n,&m,&mod);
rep(i,1,m)
{
scanf("%lld%lld",&x,&y);
add1(x,y);
}
rep(i,1,n)
if(!dfn[i])
tarjan(i);
rep(i,1,n)
for(r ll j=head1[i];j;j=e1[j].nxt)
{
ll y=e1[j].to;
if(in[i]!=in[y])
{
a[++num].x=in[i];
a[num].y=in[y];
}
}
sort(a+1,a+num+1,cmp);//去重
rep(i,1,num)
if(a[i].x!=a[i-1].x||a[i].y!=a[i-1].y)//建新图
{
add2(a[i].x,a[i].y);
++du[a[i].y];
}
topsort(); //拓扑排序DP
printf("%lld\n",res);
rep(i,1,n)
if(dp[i]==res)
tj=(tj+ans[i])%mod;
printf("%lld",tj);
return 0;
}