题目描述
一个有向图 G = ( V , E ) G=(V,E) G=(V,E)称为半连通的(Semi-Connected),如果满足: ∀ u , v ∈ V \forall u,v\in V ∀u,v∈V,满足 u → v u\to v u→v或 v → u v\to u v→u,即对于图中任意两点 u , v u,v u,v,存在一条 u u u到 v v v的有向路径或者从 v v v到 u u u的有向路径。
若 G ′ = ( V ′ , E ′ ) G'=(V',E') G′=(V′,E′)满足 V ′ ⊆ V V'\subseteq V V′⊆V, E ′ E' E′是 E E E中所有跟 V ′ V' V′有关的边,则称 G ′ G' G′是 G G G的一个导出子图。
若 G ′ G' G′是 G G G的导出子图,且 G ′ G' G′半连通,则称 G ′ G' G′为 G G G的半连通子图。若 G ′ G' G′是 G G G所有半连通子图中包含节点数最多的,则称 G ′ G' G′是 G G G的最大半连通子图。
给定一个有向图 G G G,请求出 G G G的最大半连通子图拥有的节点数 K K K,以及不同的最大半连通子图的数目 C C C。由于 C C C可能比较大,仅要求输出 C C C对 X X X的余数。
输入格式
第一行包含两个整数 N , M , X N,M,X N,M,X。 N , M N,M N,M分别表示图 G G G的点数与边数, X X X的意义如上文所述接下来 M M M行,每行两个正整数 a , b a, b a,b,表示一条有向边 ( a , b ) (a, b) (a,b)。图中的每个点将编号为 1 , 2 , 3 , ⋯   , N 1,2,3,\cdots,N 1,2,3,⋯,N,保证输入中同一个 ( a , b ) (a,b) (a,b)不会出现两次。
输出格式
应包含两行,第一行包含一个整数 K K K。第二行包含整数 C m o d    X C\mod X CmodX。
数据范围
对于20%的数据,
N
≤
18
N\le 18
N≤18;
对于60%的数据,
N
≤
10000
N\le 10000
N≤10000;
对于100%的数据,
N
≤
100000
,
M
≤
1000000
N\le 100000, M\le 1000000
N≤100000,M≤1000000;
对于100%的数据,
X
≤
1
0
8
X\le 10^8
X≤108。
分析
首先可以知道,对于一条链,肯定是半连通图,从入度为0的点开始,依次可以遍历到它之后的点;对于一个强连通图,一定是半连通图。然后再考虑原来的问题,若给定的图是个有向无环图,则求最大的半连通子图即为求最长链,个数即为求最长链的个数;因此可以将原图缩点,形成有向无环图,在这个新图里面进行拓扑排序,在过程中进行Dp,求出以每个点为结束点的最长链及其个数,最后再枚举新图中的结束点,找出最长链及个数。
需要注意的是,在缩完点建新图前将每条边记录下来,去重,防止个数记录重复。
代码
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <iomanip>
#include <queue>
using namespace std;
typedef long long LL;
const int N=100005,M=1000005;
struct Edge {
int to,nxt;
}e[M*2],e1[M*2];
struct ArrayOfEdge {
int x,y;
}ee[M];
int h[N],cnt,ans;
int h1[N],cnt1,c;
int n,m,p,du[N];
int dfn[N],low[N];
int instack[N],v[N];
int st[N],top,f[N];
int num,scc,bel[N];
LL g[N],ansn;
queue<int> q;
void Add(int x,int y) {
e[++cnt]=(Edge){y,h[x]};
h[x]=cnt;
}
void Addt(int x,int y) {
ee[++c]=(ArrayOfEdge){x,y};
}
void Add1(int x,int y) {
e1[++cnt1]=(Edge){y,h1[x]};
h1[x]=cnt1;
}
void Tarjan(int x) {
dfn[x]=low[x]=++num;
instack[x]=1;
st[++top]=x;
for (int i=h[x];i;i=e[i].nxt) {
int y=e[i].to;
if (!dfn[y]) {
Tarjan(y);
low[x]=min(low[x],low[y]);
} else if (instack[y])
low[x]=min(low[x],dfn[y]);
}
if (dfn[x]==low[x]) {
int t;
++scc;
do {
t=st[top--];
bel[t]=scc;
v[scc]++;//v为该强连通分量的点数
instack[t]=0;
} while (t!=x);
}
}
bool cmp(ArrayOfEdge a,ArrayOfEdge b) {
if (a.x!=b.x) return a.x<b.x;
return a.y<b.y;
}
int main() {
//freopen("semi.in","r",stdin);
//freopen("semi.out","w",stdout);
scanf("%d%d%d",&n,&m,&p);
for (int i=1;i<=m;i++) {
int u,v;
scanf("%d%d",&u,&v);
Add(u,v);
}
for (int i=1;i<=n;i++)
if (!dfn[i]) Tarjan(i);
for (int x=1;x<=n;x++) {
for (int i=h[x];i;i=e[i].nxt) {
int y=e[i].to;
if (bel[x]==bel[y]) continue;
Addt(bel[x],bel[y]);//记录
}
}
sort(ee+1,ee+c+1,cmp);//排序
for (int i=1;i<=c;i++) {
if (ee[i].x==ee[i-1].x&&ee[i].y==ee[i-1].y) continue;//去重
Add1(ee[i].x,ee[i].y);
du[ee[i].y]++;
}
for (int i=1;i<=scc;i++)
if (!du[i]) {
q.push(i);
f[i]=v[i];
g[i]++;
}
while (!q.empty()) {
int t=q.front();
q.pop();
ans=max(ans,f[t]);//找最长链
for (int i=h1[t];i;i=e1[i].nxt) {
int y=e1[i].to;
if (f[y]==f[t]+v[y]) {//相等时就累加方案数
g[y]=(g[y]+g[t])%p;
} else if (f[y]<f[t]+v[y]) {//DP在DAG中求最长链,f[i]=max{f[j]+v[i]}(j->i有边)
f[y]=f[t]+v[y];
g[y]=g[t];//直接更新
}
du[y]--;
if (du[y]==0) q.push(y);
}
}
printf("%d\n",ans);
for (int i=1;i<=scc;i++) {
if (f[i]==ans) {
ansn=(ansn+g[i])%p;
}
}
printf("%lld",ansn);
return 0;
}