2020-12-10

P2272 [ZJOI2007]最大半连通子图

题目链接

这道题真的是把我恶心坏了。
开了很多很多不同用处的数组,来完成这个题目。加上各种STL库的容器才弄完这个鬼东西。

根据题目意思我们可以知道,任何一个强连通分量都是半连通分量。

然后我们假象tarjan之后的图,稍加思考可以进一步得到如下结论。

如果一个强连通分量与另外一个强连通分量之间有一条边直接相连,那么这两个强连通分量可以合成一个更大的半连通分量

在tarjan完之后的图中体现为,树的一条链,所以也就是tarjan缩点之后,从一个叶子节点开始逐步向上累加直到根节点,就是当前叶子节点的最大半连通分量。然后我们可以通过拓扑排序来实现这一个操作。

			if(f[now]+num[v]>f[v]){//
				f[v]=f[now]+num[v];
				g[v]=g[now];
			}
			else if(f[now]+num[v]==f[v]){
				g[v]+=g[now];
				g[v]%=mod; 
			}

f[i]:以强连通分量i为根的子树的最大值
g[i]:以强连通分量i为根的子树的最大值的方案数目
num[i]:强连通分量i的节点个数

算法整体流程

1.输入并建图
2.tarjan缩点
3.重新建图,重新建图的过程需要注意,可能会存在重复的边

举个例子,a和b处于同一个强连通分量,但是c与a不处于同一个强连通分量。
a可以有一条边指向c,b也可以有一条边指向c,在后续建图可能会重复。

4.拓扑排序
5.统计并输出答案。

这里第一次建图使用链式前向星,第二次使用vector来建图。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int n,m,mod,cnt,tot,sum;
ll maxnum,maxx;
struct node {
	int v,next;
};
struct node edge[1000002];
stack<int>q;
int du[100002],low[100002],dfn[100002],scc[100002],ins[100002],num[100002],head[100002];
ll g[100002],f[100002];
vector<int>link[100002];
map<int,int>mp[100002];
void add(int u,int v){
	edge[tot].v=v;
	edge[tot].next=head[u];
	head[u]=tot;
	tot++;
}
void init(){
	tot=0;sum=0;cnt=0;
	for(int i=1;i<=n;i++){
		head[i]=-1;dfn[i]=-1;
	}
}
void tarjan(int now){//正常tarjan,没有其它的操作
	sum++;
	dfn[now]=low[now]=sum;
	q.push(now);
	ins[now]=1;
	for(int i=head[now];i!=-1;i=edge[i].next){
		int v=edge[i].v;
		if(dfn[v]==-1){
			tarjan(v);;
			low[now]=min(low[now],low[v]);
		}
		else if(ins[v])low[now]=min(low[now],dfn[v]);
	}
	if(dfn[now]==low[now]){
		cnt++;
		int v=-1;
		do{
			v=q.top();q.pop();
			ins[v]=0;scc[v]=cnt;num[cnt]++;
		}while(v!=now);
	}
}
void topsort(){
	queue<int>q;
	for(int i=1;i<=cnt;i++){//初始化队列和转移数组
		if(du[i]==0){
		q.push(i);
		f[i]=num[i];
		g[i]=1;			
		}
	}
	while(!q.empty()){
		int now=q.front();q.pop();
		for(int i=0;i<link[now].size();i++){
			int v=link[now][i];
			du[v]--;
			if(du[v]==0)q.push(v);
			if(f[now]+num[v]>f[v]){
				f[v]=f[now]+num[v];
				g[v]=g[now];
			}
			else if(f[now]+num[v]==f[v]){
				g[v]+=g[now];
				g[v]%=mod; 
			}
		}
	}
}
int main (){
	#ifdef LOCAL
	freopen(".\\a.in", "r", stdin);
	#endif
	scanf("%d%d%d",&n,&m,&mod);
	init();
	for(int i=1;i<=m;i++){
		int u,v;
		scanf("%d%d",&u,&v);
		add(u,v);
	}
	for(int i=1;i<=n;i++){
		if(dfn[i]==-1)tarjan(i);
	}
	for(int i=1;i<=n;i++){//遍历原来的所有边
		for(int j=head[i];j!=-1;j=edge[j].next){
			int u=i;int v=edge[j].v;
			u=scc[u];v=scc[v];//重新建图是相对于缩点之后的强连通分量而言的,要注意转换
			if(u!=v&&mp[u][v]==0){
				link[u].push_back(v);
				mp[u][v]=1;//重复边的标记
				du[v]++;
			}
		}
	}
	topsort();
	for(int i=1;i<=cnt;i++)maxx=max(maxx,f[i]);
	printf("%lld\n",maxx);
	for(int i=1;i<=cnt;i++){
		if(f[i]==maxx)maxnum+=g[i];
		maxnum%=mod;
	}
	printf("%lld\n",maxnum);
	return 0;
}
Eclipse 2020-12 网盘是一款用于文件存储和共享的云存储服务。它为用户提供了一个安全的存储空间,可以将文件上传至云端,并在需要的时候随时访问和共享。 Eclipse 2020-12 网盘的使用非常简便,用户只需在注册账户后,即可开始上传文件。用户可以将各种类型的文件上传至网盘中,包括文档、图片、音频、视频等。上传完成后,用户可以根据需要创建文件夹和子文件夹来组织文件,并设置文件的共享权限。 Eclipse 2020-12 网盘支持多种共享方式。用户可以通过生成分享链接,将文件发送给他人,让他人可以通过链接下载文件。此外,用户还可以将文件共享给其他账户,让其他用户可以直接在他们的网盘中查看和下载文件。用户可以根据需要设置分享链接的有效期限和密码保护,确保文件的安全性。 Eclipse 2020-12 网盘还提供了文件版本控制功能。用户可以在文件上传后,随时更新文件的版本,并回溯到之前的版本。这对于文件的修改和追溯非常有帮助,而且可以有效避免文件的丢失或覆盖。 总的来说,Eclipse 2020-12 网盘是一款功能强大、易于使用的云存储服务。它提供了安全可靠的文件存储空间,同时支持多种共享方式和文件版本控制功能。无论是个人用户还是企业用户,都可以通过使用Eclipse 2020-12 网盘来管理和共享文件,提高工作效率。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值