AcWing 1175. 最大半连通子图 题解(tarjan缩点拓扑序dp)

11 篇文章 0 订阅
4 篇文章 0 订阅
本文介绍了一种求解最大半连通子图的问题,通过Tarjan算法求解强连通分量,并根据强连通分量重新构建图,最终找到具有最大点数的半连通子图。在解决过程中,使用了动态规划优化方案数的计算,避免了重复计数。代码实现中涉及到图的邻接表存储、哈希表判重等技巧。
摘要由CSDN通过智能技术生成

AcWing 1175. 最大半连通子图

#include<bits/stdc++.h>

using namespace std;

typedef long long ll;

const int N = 1e5 + 10, M = 2e6 + 10;

int n, m, mod;
int h[N], e[M], ne[M], idx;
int hs[N];  //用来构建强连通分量为点的图
int low[N], dfn[N], timep;  //求强连通分量
int stk[N], top;
bool is_stk[N];
int id[N];
int scc_cnt;
int f[N], g[N];  //分别记录点数和方案数 
int Size[N];
 
void add(int h[], int a, int b){
	e[idx] = b;
	ne[idx] = h[a];
	h[a] = idx ++ ;
}

void tarjan(int u){
	low[u] = dfn[u] = ++ timep;
	stk[ ++ top] = u;
	is_stk[u] = true;
	
	for(int i = h[u]; ~i; i = ne[i]){
		int j = e[i];
		if(!dfn[j]){
			tarjan(j);
			low[u] = min(low[u], low[j]);
		}
		else if(is_stk[j]) low[u] = min(low[u], dfn[j]);
	}
	
	if(dfn[u] == low[u]){
		int y;
		++ scc_cnt;
		do{
			y = stk[top -- ];
			id[y] = scc_cnt;
			Size[scc_cnt] ++ ;
			is_stk[y] = false;
		}while(u != y);
	}
}

int main()
{
	cin>>n>>m>>mod;
	memset(h, -1, sizeof h);
	memset(hs, -1, sizeof hs);
	for(int i = 0; i < m; i ++ ){
		int a, b;
		cin>>a>>b;
		add(h, a, b);
	}
	
	for(int i = 1; i <= n; i ++ ){
		if(!dfn[i]){
			tarjan(i);
		}
	}
	
	//根据最强连通分量建图
	unordered_set<ll>S;  //计算点数和方案数的时候是按照点进行加和,
	//重边会导致多条路径相同的边相同的点被算多次,影响结果,所以要判重 
	for(int i = 1; i <= n; i ++ ){
		for(int j = h[i]; ~j; j = ne[j]){
			int k = e[j];
			int a = id[i], b = id[k];
			ll Hash = a * 1000000ll + b;  //给边赋哈希值 
			if(a != b && !S.count(Hash)){  //如果这个边还没有用于建图 
				add(hs, a, b);  //建图
				S.insert(Hash); 
			}
		} 
	}
	
	for(int i = scc_cnt; i; i -- ){
		if(!f[i]){
			f[i] = Size[i];
			g[i] = 1;
		}
		for(int j = hs[i]; ~j; j = ne[j]){
			int k = e[j];
			if(f[k] < f[i] + Size[k]){
				f[k] = f[i] + Size[k];
				g[k] = g[i];  //更新方案数,直接由i点到达k点,所以不存在增加方案数的情况 
			}
			else if(f[k] == f[i] + Size[k]) g[k] = (g[k] + g[i]) % mod;  //两个点的状态点数相同,所以方案数累加 
		}
	}
	
	int sum = 0, op = 0;
	for(int i = 1; i <= scc_cnt; i ++ ){
		if(f[i] > op){
			op = f[i];
			sum = g[i];
		}
		else if(op == f[i]) sum = (sum + g[i]) % mod;
	}
	
	cout<<op<<endl;
	cout<<sum<<endl;
	
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值