【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.强连通必然是弱连通的;

一条链上的点弱连通。

时间: 2024-08-07 17:00:04

【BZOJ 1093】 [ZJOI2007]最大半连通子图的相关文章

BZOJ 1093: [ZJOI2007]最大半连通子图( tarjan + dp )

WA了好多次... 先tarjan缩点, 然后题意就是求DAG上的一条最长链. dp(u) = max{dp(v)} + totu, edge(u,v)存在. totu是scc(u)的结点数. 其实就是记忆化搜一下...重边就用set判一下 ------------------------------------------------------------------------------------------- #include<cstdio> #include<cstring

bzoj 1093 [ZJOI2007]最大半连通子图(scc+DP)

1093: [ZJOI2007]最大半连通子图 Time Limit: 30 Sec  Memory Limit: 162 MBSubmit: 2286  Solved: 897[Submit][Status][Discuss] Description Input 第一行包含两个整数N,M,X.N,M分别表示图G的点数与边数,X的意义如上文所述.接下来M行,每行两个正整数a, b,表示一条有向边(a, b).图中的每个点将编号为1,2,3…N,保证输入中同一个(a,b)不会出现两次. Outpu

BZOJ 1093 ZJOI2007 最大半连通子图 Tarjan+动态规划

题目大意:定义半连通子图为一个诱导子图,其中任意两点(x,y)中x可到达y或y可到达x,求最大半连通子图的大小以及方案数 不就是个缩点之后拓扑序DP求最长链么 这题意逗不逗233333 注意缩点后连边不要连重复了 判重边那里我用了set... #include <set> #include <cstdio> #include <cstring> #include <iostream> #include <algorithm> #define M

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

题意:一个有向图中,若对于任意两个点s和t,要么存在一条从s到t的路径,要么存在一条t到s的路径(当然两条路径都存在也是可以的),那么称这个有向图是一个半联通图.现在给定一个有向图,求出该有向图中点数最多的半联通子图的点数以及个数. 很显然,在一个最优方案中,同一个强连通分量中的点要么都选,要么都不选. 很自然地,先用tarjan缩个点,得到一个有向无环图,每个点的点权为对应的强连通分量的点的个数. 然后稍微脑补一下,就知道第一问的答案显然是该有向无环图上最长链的长度,而第二问的答案自然也就是最

[bzoj 1093][ZJOI2007]最大半联通子图(强联通缩点+DP)

题目:http://www.lydsy.com:808/JudgeOnline/problem.php?id=1093 分析: 首先肯定是先把强联通全部缩成一个点,然后成了一个DAG 下面要知道一点:原图的最大半联通子图实际是上是新DAG图的一个最长链 然后就像拓扑排序一样(不过这是以出度为0的点优先,拓扑排序以入度为0的点优先),设f[i]表示以节点i为起始节点的最长链的长度,s[i]表示以节点i为起始节点的最长链的条数,然后就DP一样搞…… 注意: 1.缩点的时候有可能有重边,要注意判断 2

【BZOJ】1093: [ZJOI2007]最大半连通子图(tarjan+拓扑序)

http://www.lydsy.com/JudgeOnline/problem.php?id=1093 两个条件综合起来加上求最大的节点数,那么很明显如果是环一定要缩点. 然后再仔细思考下就是求dag的最长路的数目啦... 然后wa了... 看了题解...噗!第一次注意到缩点后会有重边QAQ...于是.. orz orz 然后思考了下怎么处理重边...很简单,每个点bfs时记录一下就行了.. #include <cstdio> #include <cstring> #includ

[ZJOI2007]最大半连通子图

[ZJOI2007]最大半连通子图 题目大意: 一个有向图称为半连通的,当且仅当对于任意两点\(u,v\),都满足\(u\)能到达\(v\)或者\(v\)能到达\(u\). 给定一个\(n(n\le10^5)\)个点,\(m(m\le10^6)\)条边的有向图, 问该图最大半连通子图的节点个数及方案数. 思路: 缩点后在DAG上DP求带点权最长链,并统计方案数即可. 源代码: #include<stack> #include<queue> #include<cstdio>

【tarjan 拓扑排序 dp】bzoj1093: [ZJOI2007]最大半连通子图

思维难度不大,关键考代码实现能力.一些细节还是很妙的. Description 一个有向图G=(V,E)称为半连通的(Semi-Connected),如果满足:?u,v∈V,满足u→v或v→u,即对于图中任意两点u,v,存在一条u到v的有向路径或者从v到u的有向路径.若G'=(V',E')满足V'?V,E'是E中所有跟V'有关的边,则称G'是G的一个导出子图.若G'是G的导出子图,且G'半连通,则称G'为G的半连通子图.若G'是G所有半连通子图中包含节点数最多的,则称G'是G的最大半连通子图.给

[ZJOI2007]最大半连通子图 (Tarjan缩点,拓扑排序,DP)

题目链接 Solution 大概是个裸题. 可以考虑到,如果原图是一个有向无环图,那么其最大半联通子图就是最长的一条路. 于是直接 \(Tarjan\) 缩完点之后跑拓扑序 DP就好了. 同时由于是拓扑序DP,要去掉所有的重边. Code #include<bits/stdc++.h> #define ll long long using namespace std; const int maxn=100008; struct sj{int to,next;}a[maxn*10]; ll mo