P2272 [ZJOI2007]最大半连通子图

文章目录

        • R e s u l t Result Result
        • H y p e r l i n k Hyperlink Hyperlink
        • D e s c r i p t i o n Description Description
        • S o l u t i o n Solution Solution
        • C o d e 1 Code1 Code1
        • C o d e 2 Code2 Code2

R e s u l t Result Result

Solution1:
P2272 [ZJOI2007]最大半连通子图_第1张图片
Solution2:
P2272 [ZJOI2007]最大半连通子图_第2张图片
其实第一种解法是先AC的,但是因为输出格式弄错找了半天错误,换了一种解法才发现自己输出错了。。。


H y p e r l i n k Hyperlink Hyperlink

https://www.luogu.com.cn/problem/P2272


D e s c r i p t i o n Description Description

定义一张半联通子图为满足在这张图内的任意两点 i , j i,j i,j i i i可以到达 j j j或者 j j j可以到达 i i i

求一张有 n n n个节点 m m m条边的有向图的最大半联通子图及其方案数

数据范围:
n ≤ 1 0 5 , m ≤ 1 0 6 n\leq 10^5,m\leq 10^6 n105,m106


S o l u t i o n Solution Solution

显然一张联通子图必然是半联通子图

所以我们先用 T a r j a n Tarjan Tarjan缩点,缩点后可能会产生自环或重边,它们各有处理方法,代码中会有注释

然后稍加分析,容易发现,最大半联通子图实际上是缩点后这张 D A G DAG DAG上的最长链!
那么我们就可以在这个 D A G DAG DAG d p dp dp

f i , 0 / 1 f_{i,0/1} fi,0/1表示以第 i i i个点结尾的最长链长度及其对应的方案数,用 d f s dfs dfs或者拓扑序 d p dp dp处理均可

时间复杂度:
用离散化处理重边复杂度是 O ( n + m l o g m ) O(n+mlogm) O(n+mlogm)
用数组保存访问的点处理重边复杂度是 O ( n + m ) O(n+m) O(n+m)
代码1采用前者,代码2采用后者


C o d e 1 Code1 Code1

#include
#include
#include
#include
#include
#define N 100010
#define M 2000010
#define LL long long
using namespace std;int n,m,le[N],lg[N],tote,totg,a,b,mod;
struct node{
     int next,to;}e[M],g[M];
inline void adde(int u,int v){
     e[++tote]=(node){
     le[u],v};le[u]=tote;return;}
inline void addg(int u,int v){
     g[++totg]=(node){
     lg[u],v};lg[u]=totg;return;}
bool vis[N];
inline LL read()             
{
     
	char c;LL d=1,f=0;
	while(c=getchar(),!isdigit(c)) if(c=='-') d=-1;f=(f<<3)+(f<<1)+(c^48);
	while(c=getchar(),isdigit(c)) f=(f<<3)+(f<<1)+(c^48);
	return d*f;
}
int low[N],dfn[N],stk[N],top,cnt,which[N],k,large[N];
inline void Tarjan(int x)
{
     
	low[x]=dfn[x]=++cnt;
	stk[++top]=x;vis[x]=true;
	for(register int i=le[x];i;i=e[i].next)
	{
     
		int y=e[i].to;
		if(dfn[y]==0)
		{
     
			Tarjan(y);
			low[x]=min(low[x],low[y]);
		}
		else if(vis[y]) low[x]=min(low[x],low[y]);
	}
	if(dfn[x]==low[x])
	{
     
		int y;
		which[x]=++k;large[k]++;
		while(y=stk[top--])
		{
     
			vis[y]=false;
			if(x==y) break;
			large[k]++;which[y]=k;
		}
		return;
	}
}
struct edge{
     int id,u,v;}E[M];
inline bool cmp(edge x,edge y) {
     return x.u<y.u||x.u==y.u&&x.v<y.v;}
int len,rd[N],cd[N],f[N][2],maxn;
inline void dfs(int x)//dfs遍历,顺便做dp
{
     
	vis[x]=true;
	if(cd[x]==0)
	{
     
		f[x][0]=large[x];f[x][1]=1;
		maxn=max(maxn,f[x][0]);
		return;
	}
	for(register int i=lg[x];i;i=g[i].next)
	{
     
		int y=g[i].to;
		if(vis[y]==0) dfs(y);
		if(f[y][0]+large[x]>f[x][0]) f[x][0]=f[y][0]+large[x],f[x][1]=f[y][1];
		else if(f[y][0]+large[x]==f[x][0]) (f[x][1]+=f[y][1])%=mod;
		maxn=max(maxn,f[x][0]);
	}
	return;
}
signed main()
{
     
	n=read();m=read();mod=read();
	for(register int i=1;i<=m;i++) a=read(),b=read(),adde(a,b);
	for(register int i=1;i<=n;i++) if(!dfn[i]) Tarjan(i);
	for(register int i=1;i<=n;i++)
	 for(register int j=le[i];j;j=e[j].next)
	{
     
		int x=which[i],y=which[e[j].to];
		if(x==y) continue;//处理自环
		E[++len]=(edge){
     len,x,y};//把边存下来,其实第一维是没必要的,当时以为要用而已。。。
	}
	sort(E+1,E+1+len,cmp);
	for(register int i=1;i<=len;i++) 
	 if(E[i].u!=E[i-1].u||E[i].v!=E[i-1].v) addg(E[i].u,E[i].v),++rd[E[i].v],++cd[E[i].u];//处理重边
	for(register int i=1;i<=k;i++) if(rd[i]+vis[i]==0) dfs(i);
	int res=0;
	for(register int i=1;i<=k;i++) if(f[i][0]==maxn) (res+=f[i][1])%=mod;
	printf("%d\n%d",maxn,res);
}

C o d e 2 Code2 Code2

#include
#include
#include
#include
#include
#define N 100010
#define M 2000010
#define LL long long
using namespace std;int n,m,le[N],lg[N],tote,totg,a,b,mod,fa[N];
struct node{
     int next,to;}e[M],g[M];
inline void adde(int u,int v){
     e[++tote]=(node){
     le[u],v};le[u]=tote;return;}
inline void addg(int u,int v){
     g[++totg]=(node){
     lg[u],v};lg[u]=totg;return;}
bool vis[N];
inline LL read()             
{
     
	char c;LL d=1,f=0;
	while(c=getchar(),!isdigit(c)) if(c=='-') d=-1;f=(f<<3)+(f<<1)+(c^48);
	while(c=getchar(),isdigit(c)) f=(f<<3)+(f<<1)+(c^48);
	return d*f;
}
int low[N],dfn[N],stk[N],top,cnt,which[N],k,large[N];
inline void Tarjan(int x)
{
     
	low[x]=dfn[x]=++cnt;
	stk[++top]=x;vis[x]=true;
	for(register int i=le[x];i;i=e[i].next)
	{
     
		int y=e[i].to;
		if(dfn[y]==0)
		{
     
			Tarjan(y);
			low[x]=min(low[x],low[y]);
		}
		else if(vis[y]) low[x]=min(low[x],low[y]);
	}
	if(dfn[x]==low[x])
	{
     
		int y;
		which[x]=++k;large[k]++;
		while(y=stk[top--])
		{
     
			vis[y]=false;
			if(x==y) break;
			large[k]++;which[y]=k;
		}
		return;
	}
}
struct edge{
     int u,v;}E[M];
inline bool cmp(edge x,edge y) {
     return x.u<y.u||x.u==y.u&&x.v<y.v;}
int f[N][2],maxn;
signed main()
{
     
	n=read();m=read();mod=read();
	for(register int i=1;i<=m;i++) a=read(),b=read(),adde(a,b);
	for(register int i=1;i<=n;i++) if(!dfn[i]) Tarjan(i);
	for(register int i=1;i<=n;i++)
	{
     
		f[i][0]=large[i];f[i][1]=1;
		for(register int j=le[i];j;j=e[j].next)
		{
     
			int x=which[i],y=which[e[j].to];
			if(x==y) continue;//处理自环
			addg(x,y);
		}
	}
	for(register int x=k;x>=1;x--)//缩点的顺序就是逆拓扑序,可以直接倒序循环
	 for(register int i=lg[x];i;i=g[i].next)
	{
     
		int y=g[i].to;
		if(fa[y]==x) continue;//处理重边,保证每条边最多被用来转移一次
		fa[y]=x;
		if(f[y][0]<f[x][0]+large[y]) f[y][0]=f[x][0]+large[y],f[y][1]=f[x][1];
		else if(f[y][0]==f[x][0]+large[y]) (f[y][1]+=f[x][1])%=mod;
	}
	int res=0;
	for(register int i=1;i<=k;i++) 
	 if(f[i][0]>maxn) maxn=f[i][0],res=f[i][1];
	 else if(f[i][0]==maxn) (res+=f[i][1])%=mod;
	printf("%d\n%d",maxn,res);
}

你可能感兴趣的:(P2272,ZJOI2007,最大半联通子图)