首页 > 代码库 > 【BZOJ 1093】 [ZJOI2007]最大半连通子图

【BZOJ 1093】 [ZJOI2007]最大半连通子图

1093: [ZJOI2007]最大半连通子图

Time Limit: 30 Sec  Memory Limit: 162 MB
Submit: 1732  Solved: 679
[Submit][Status]

Description

技术分享

Input

第一行包含两个整数N,M,X。N,M分别表示图G的点数与边数,X的意义如上文所述。接下来M行,每行两个正整数a, b,表示一条有向边(a, b)。图中的每个点将编号为1,2,3…N,保证输入中同一个(a,b)不会出现两次。

Output

应包含两行,第一行包含一个整数K。第二行包含整数C Mod X.

Sample Input

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

Sample Output

3
3

HINT

对于100%的数据, N ≤100000, M ≤1000000;对于100%的数据, X ≤10^8。


tarjan缩点+topsort。


强连通的必然是半连通的,那么首先缩点,于是图就变成一条一条的链了。


接下来只要找最长链,并统计最长链的条数即可。


具体见代码。


#include <iostream>
#include <cstring>
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <stack>
#include <queue>
#define M 100005
using namespace std;
queue<int> q;
int tot=0,n,m,v[M],sccno[M],mod,pre[M],h[M],lowlink[M],dfs_clock,scc_cnt;
int du[M],d[M],H[M],cnt[M],num[M];
stack<int> S;
queue<int> scc[M];
struct edge
{
	int y,ne;
}e[M*15],E[M*15];
void read(int &tmp)
{
	tmp=0;
	char ch=getchar();
	int fu=1;
	for (;ch<'0'||ch>'9';ch=getchar())
		if(ch=='-') fu=-1;
	for (;ch>='0'&&ch<='9';ch=getchar())
		tmp=tmp*10+ch-'0';
	tmp*=fu;
}
void Addedge(int x,int y)
{
	e[++tot].y=y;
	e[tot].ne=h[x];
	h[x]=tot;
}
void dfs(int x)
{
	pre[x]=lowlink[x]=++dfs_clock;
	S.push(x);
	for (int i=h[x];i;i=e[i].ne)
	{
		int y=e[i].y;
		if (!pre[y])
		{
			dfs(y);
			lowlink[x]=min(lowlink[x],lowlink[y]);
		}
		else
			if (!sccno[y])
				lowlink[x]=min(lowlink[x],pre[y]);
	}
	if (pre[x]==lowlink[x])
	{
		scc_cnt++;
		while (1)
		{
			int y=S.top();
			S.pop();
			cnt[scc_cnt]++;
			scc[scc_cnt].push(y);
			sccno[y]=scc_cnt;
			if (y==x) break;
		}
	}
}
void Findscc()
{
	dfs_clock=scc_cnt=0;
	for (int i=1;i<=n;i++)
		pre[i]=0,sccno[i]=0;
	for (int i=1;i<=n;i++)
		if (!pre[i]) dfs(i);
}
void Addedge2(int x,int y)
{
	E[++tot].y=y;
	E[tot].ne=H[x];
	H[x]=tot;
}
void Buildgragh()
{
	tot=0;
	for (int i=1;i<=scc_cnt;i++)
	{
		while (!scc[i].empty())
		{
			int j=scc[i].front();
			scc[i].pop();
			for (int k=h[j];k;k=e[k].ne)
			{
				int y=sccno[e[k].y];
				if (y==i||v[y]==1) continue;
				v[y]=1;
				q.push(y);
			}
		}
		while (!q.empty())
		{
			int x=q.front();
			du[x]++;
			q.pop();
			Addedge2(i,x);
			v[x]=0;
		}
	}
}
void Topsort()
{
	int ma=0;
	tot=0;
	for (int i=1;i<=scc_cnt;i++)
		if (!du[i]) 
		{
			q.push(i);
			d[i]=cnt[i];
			num[i]=1;
			if (d[i]>ma) ma=d[i];
		}
	while (!q.empty())
	{
		int x=q.front();
		q.pop();
		for (int i=H[x];i;i=E[i].ne)
		{
			int y=E[i].y;
			du[y]--;
			if (d[y]==d[x]+cnt[y]) num[y]=(num[y]+num[x])%mod;
			if (d[y]<d[x]+cnt[y]) 
			{
				d[y]=d[x]+cnt[y];
				num[y]=num[x];
				if (ma<d[y]) ma=d[y];
			}
			if (!du[y]) q.push(y);
		}
	}
	for (int i=1;i<=scc_cnt;i++)
		if (d[i]==ma) tot=(tot+num[i])%mod;
	printf("%d\n%d\n",ma,(tot+mod)%mod);
}
int main()
{
        read(n),read(m),read(mod);
	for (int i=1;i<=m;i++)
	{
		int x,y;
		read(x),read(y);
		Addedge(x,y);
	}
	Findscc();
	Buildgragh();
	Topsort();
	return 0;
}

技术分享


感悟:

1.一开始wa了无数次:

要记录每个强连通分量的点数,更新d[]数组用点数更新,而不是直接+1;


对于方案数num[],也是不能直接+1的,如果当前的d是最大值,才更新,更新的时候要加前一个点的num;如果他更新了最大值,当前点的num等于前一个点的num


2.强连通必然是弱连通的;

一条链上的点弱连通。

【BZOJ 1093】 [ZJOI2007]最大半连通子图