「ZJOI2007」 最大半连通子图 - Tarjan缩点+拓扑排序

题目描述

一个有向图 G = ( V , E ) G=(V,E) G=(V,E)称为半连通的(Semi-Connected),如果满足: ∀ u , v ∈ V \forall u,v\in V u,vV,满足 u → v u\to v uv v → u v\to u vu,即对于图中任意两点 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 VV 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 N18
对于60%的数据, N ≤ 10000 N\le 10000 N10000
对于100%的数据, N ≤ 100000 , M ≤ 1000000 N\le 100000, M\le 1000000 N100000,M1000000
对于100%的数据, X ≤ 1 0 8 X\le 10^8 X108

分析

首先可以知道,对于一条链,肯定是半连通图,从入度为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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值