[ZJOI2007]最大半连通子图

题目描述

一个有向图\(G=(V,E)\)称为半连通的(Semi-Connected),如果满足:\(\forall u,v\in V\),满足\(u\leadsto v\)\(v\leadsto u\),即对于图中任意两点\(u\)\(v\),存在一条\(u\)\(v\)的有向路径或者从\(v\)\(u\)的有向路径。
\(G'=(V',E')\)满足\(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\),表示一条有向边\((a,b)\)。图中的每个点将编号为\(1,2,\cdots,N\),保证输入中同一个\((a,b)\)不会出现两次。


输出格式

第一行包含一个整数\(K\)
第二行包含整数\(C\ Mod\ X\)


输入输出样例

输入

6 6 20070603
1 2
2 1
1 3
2 4
5 6
6 4

输出

3
3

说明/提示

对于\(100\%\)的数据,\(N\le 10^5\)\(M\le 10^6\)\(X\le 10^8\)



强连通分量+动态规划

强连通分量

在有向图\(G\)中,如果两个顶点\(u_i\)\(u_j\)间有一条从\(u_i\)\(u_j\)的有向路径,同时还有一条从\(u_j\)\(u_i\)的有向路径,则称两个顶点强连通(strongly connected)。如果有向图\(G\)的每两个顶点都强连通,称\(G\)是一个强连通图。有向图的极大强连通子图,称为强连通分量(strongly connected components)。
显然,对于任意一个强连通分量\(S\in G\),它都是一个半连通子图,于是我们可以考虑先缩点。

动态规划

缩点之后,原图变成了一个DAG(有向无环图),显然,对于任意一条DAG中的链,它都是一个半连通子图,所以题目中的\(K\)就是DAG中最大链的顶点个数,\(C\)就是DAG中不同的最大链有多少个。
\(size(u)\)表示顶点\(u\)代表的强连通分量\(S\)中的顶点个数,\(f(u)\)表示从顶点\(u\)开始延伸的最大链的顶点个数,\(d(u)\)表示从顶点\(u\)开始延伸的不同的最大链有多少个,则有:
\(f(u)=size(u)+max(f(v))\quad(u\to v)\)
\(d(u)=\sum d(v)\quad(u\to v,size(u)+f(v)=f(u))\)
每次从一个没有被访问过的顶点开始做深度优先搜索,回溯时计算每个顶点的\(f\)\(d\)值。\(K\)\(C\)也可以按照相同的方式进行更新。


代码

#include<map>
#include<stack>
#include<cstdio>
#include<iostream>
using namespace std;
const int MAXN=(int)1e5+5,MAXM=(int)1e6+5;
map<int,bool>M[MAXN];
stack<int>S;
int lw[MAXN],siz[MAXN],scc[MAXN],f[MAXN],d[MAXN];
int hed1[MAXN],nxt1[MAXM],to1[MAXM],hed2[MAXN],nxt2[MAXM],to2[MAXM];
bool vis[MAXN];
int x,cnt,tim,scclen,mx,ans;
inline void add1(int a,int b){
	nxt1[++cnt]=hed1[a];
	to1[cnt]=b;
	hed1[a]=cnt;
}
void dfs1(int u){
	int dfn=lw[u]=++tim;
	S.push(u);
	for(register int i=hed1[u];i;i=nxt1[i]){
		if(!lw[to1[i]])
			dfs1(to1[i]);
		if(!scc[to1[i]])
			lw[u]=min(lw[u],lw[to1[i]]);
	}
	if(!(lw[u]^dfn)){
		scclen++;
		while(1){
			int pnt=S.top();
			S.pop();
			siz[scclen]++;
			scc[pnt]=scclen;
			if(!(pnt^u))
				break;
		}
		f[scclen]=siz[scclen];
		d[scclen]=1;
	}
}
inline void add2(int a,int b){
	nxt2[++cnt]=hed2[a];
	to2[cnt]=b;
	hed2[a]=cnt;
}
void dfs2(int u){
	vis[u]=1;
	for(register int i=hed2[u];i;i=nxt2[i]){
		if(!vis[to2[i]])
			dfs2(to2[i]);
		if(f[to2[i]]+siz[u]>f[u]){
			f[u]=f[to2[i]]+siz[u];
			d[u]=d[to2[i]];
		}
		else if(!(f[to2[i]]+siz[u]^f[u]))
			(d[u]+=d[to2[i]])%=x;
	}
	if(f[u]>mx){
		mx=f[u];
		ans=d[u];
	}
	else if(!(f[u]^mx))
		(ans+=d[u])%=x;
}
int main(){
	int n,m;
	scanf("%d%d%d",&n,&m,&x);
	for(register int i=1;i<=m;i++){
		int a,b;
		scanf("%d%d",&a,&b);
		add1(a,b);
	}
	for(register int i=1;i<=n;i++)
		if(!lw[i])
			dfs1(i);
	cnt=0;
	for(register int i=1;i<=n;i++)
		for(register int j=hed1[i];j;j=nxt1[j])
			if(scc[i]^scc[to1[j]]&&!M[scc[i]][scc[to1[j]]]){
				add2(scc[i],scc[to1[j]]);
				M[scc[i]][scc[to1[j]]]=1;
			}
	for(register int i=1;i<=scclen;i++)
		if(!vis[i])
			dfs2(i);
	printf("%d\n%d\n",mx,ans);
	return 0;
}

posted @ 2019-10-02 13:12  坚如钻石  阅读(334)  评论(0)    收藏  举报