[FROM LUOGU][ZJOI2007]最大半连通子图

该博客介绍了如何解决[ZJOI2007]最大半连通子图问题,通过Tarjan算法求解强连通分量,并在缩点后使用拓扑序动态规划寻找最长链。博主详细阐述了状态转移方程,以及在新图中处理路径更新的过程,强调了避免新图中出现重边的重要性。
摘要由CSDN通过智能技术生成

[ZJOI2007]最大半连通子图

传送门

SOL
题意:在有向图G当中找到最长链,并且统计最长链数量取模
Tarjan求了SCC之后,对于每一个连通分量,其中的点都可以两两到达,缩点之后可以保证图上只剩下链
对于新图,可以考虑按拓扑序DP
设num[i]表示i为链底的最长链长度,dp[i]表示i为链底的最长链个数,siz[i]表示原图i对应的连通分量大小
当搜索到u->v的路径时:
如果num[v]<num[u]+siz[v],则dp[v]=dp[u],num[v]=num[u]+siz[v]
如果num[v]=num[u]+siz[v],则dp[v]+=dp[u]
更新的过程中可以对num取max
最后统计答案
注意:某些加边方式可能导致新图上可能有重边

代码:

#include<bits/stdc++.h>
#define N 100005
#define M 1000005
#define ll long long
#define re register
using namespace std;
inline int rd(){
	int data=0;static char ch=0;ch=getchar();
	while(!isdigit(ch))ch=getchar();
	while(isdigit(ch))data=(data<<3)+(data<<1)+ch-'0',ch=getchar();
	return data;
}
int dfn[N],low[N],vis[N],scc[N],siz[N],stk[N],tcnt,ldx,scnt;
int n,m,mod,acnt,ans,bcnt,first[N],fir[N],mx,num[N],dp[N],du[N];
struct node{int u,v,nxt;}e[M],p[M];
inline void add(int u,int v){e[++acnt].u=u;e[acnt].v=v;e[acnt].nxt=first[u];first[u]=acnt;}
inline void _add(int u,int v){p[++bcnt].u=u;p[bcnt].v=v;p[bcnt].nxt=fir[u];fir[u]=bcnt;} 
inline void dfs(int u){
	stk[++ldx]=u;vis[u]=1;dfn[u]=low[u]=++tcnt;
	for(int re i=first[u];i;i=e[i].nxt){
		int re v=e[i].v;
		if(!dfn[v]){dfs(v);low[u]=min(low[u],low[v]);}
		else if(vis[v])low[u]=min(low[u],dfn[v]);
	}
	if(low[u]==dfn[u]){
		vis[u]=0;scc[u]=++scnt;siz[scnt]++;
		while(u!=stk[ldx]){
			vis[stk[ldx]]=0;
			siz[scnt]++;
			scc[stk[ldx]]=scnt;
			ldx--;
		}ldx--;
	}
}
queue<int>q;
int f[N];
inline void topsort(){
	while(!q.empty()){
		int re u=q.front();q.pop();
		mx=max(mx,num[u]);
		for(int re i=fir[u];i;i=p[i].nxt){
			int re v=p[i].v;
			du[v]--;if(!du[v])q.push(v);
			if(f[v]==u)continue;
			if(num[v]<num[u]+siz[v])num[v]=num[u]+siz[v],dp[v]=dp[u];
			else if(num[v]==num[u]+siz[v])dp[v]=(dp[v]+dp[u])%mod;
			f[v]=u;
		}
	}
}
int main(){
	n=rd();m=rd();mod=rd();
	for(int re i=1;i<=m;i++){int re u=rd(),v=rd();add(u,v);}
	for(int re i=1;i<=n;i++)if(!dfn[i])dfs(i);
	for(int re i=1;i<=m;i++){
		e[i].u=scc[e[i].u],e[i].v=scc[e[i].v];
		if(e[i].u!=e[i].v)_add(e[i].u,e[i].v),du[e[i].v]++;
	}
	for(int re i=1;i<=scnt;i++)if(!du[i])q.push(i),num[i]=siz[i],dp[i]=1;
	topsort();
	for(int re i=1;i<=scnt;i++)if(num[i]==mx)ans=(ans+dp[i])%mod;
	cout<<mx<<endl<<ans;
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值