【题解】CF22E Scheme

题面传送门

解决思路

这里提供一种不受到边数限制的做法,比较无脑。

先考虑对于 DAG 最少添加多少边可以强联通。设入度为 0 的点的数量为 cntin,出度为 0 的点的数量为 cntout,结论是:若 DAG 连通,添加边数为 max(cntin,cntout)1,否则为 max(cntin,cntout)

如何得到?可以参考下面的连边方式:

对于每一个连通块,没有入度的点间相邻连边,没有出度的点间相邻连边。不同连通块之间,相邻的没有出度的连到没有入度的。

所以我们需要做的:

  1. 将原图转化为 DAG,可以使用 Tarjan 缩点,这里不细讲。

  2. DAG 分成一个个连通块,找出各连通块中没有入度的点、没有出度的点。

  3. 按照上述方法连边。

具体细节可以看代码。

AC Code

//If, one day, I finally manage to make my dreams a reality...
//I wonder, will you still be there by my side?
#include<bits/stdc++.h>
#define IOS ios::sync_with_stdio(false)
#define TIE cin.tie(0),cout.tie(0)
#define int long long
#define y1 cyy
#define fi first
#define se second
#define cnt1(x) __builtin_popcount(x)
#define mk make_pair
#define pb push_back
#define pii pair<int,int>
#define ls(x) (x<<1)
#define rs(x) (x<<1|1)
#define lbt(x) (x&(-x))
using namespace std;
int n,v,dfn[200005],low[200005],col[200005],id[200005],stk[200005];
int timer,tot,cnt,top,in[200005],out[200005],head[200005],Tot;
int U[200005],V[200005];
bool vis[200005],mark[200005];
vector<int> a[200005];
vector<pii> ans;
struct ltk{
	vector<int> nin,nout;  //连通块中没有入度、没有出度的点集
}l[200005];
struct node{
	int to,nxt;
}e[200005];
void add(int u,int v){
	e[++tot]={v,head[u]},head[u]=tot;
	U[tot]=u,V[tot]=v;  //要重新建图,所以记边
}
void tarjan(int x){
	dfn[x]=low[x]=++timer,stk[++top]=x,vis[x]=1;
	for(int i=head[x];i;i=e[i].nxt){
		int tmp=e[i].to;
		if(!dfn[tmp]){
			tarjan(tmp);
			low[x]=min(low[x],low[tmp]);
		}
		else if(vis[tmp]) low[x]=min(low[x],dfn[tmp]);
	}
	if(dfn[x]==low[x]){
		cnt++,id[cnt]=x;   //id[i]表示强连通分量 i 的代表点
		do{
			col[stk[top]]=cnt,vis[stk[top--]]=0;
		}while(stk[top+1]!=x);
	}
}
void dfs(int x,int c){
	if(!in[x]) l[c].nin.pb(id[x]);   //加入没有入度点
	if(!out[x]) l[c].nout.pb(id[x]);   //加入没有出度点
	for(int i=0;i<a[x].size();i++){
		int tmp=a[x][i];
		if(!mark[tmp]) mark[tmp]=1,dfs(tmp,c);
	}
}
signed main(){
	IOS;TIE;
	cin>>n;
	for(int i=1;i<=n;i++) cin>>v,add(i,v);
	for(int i=1;i<=n;i++){
		if(!dfn[i]) top=0,tarjan(i);
	}
	if(cnt==1){   //注意特判:如果原图已经是一个强连通分量,不用加边
		cout<<0<<endl;
		return 0;
	}
	for(int i=1;i<=tot;i++){
		int x=col[U[i]],y=col[V[i]];
		if(x^y) a[x].pb(y),a[y].pb(x),in[y]++,out[x]++;   //重新建图
	}
	for(int i=1;i<=cnt;i++){
		if(!mark[i]) mark[i]=1,dfs(i,++Tot);   //找连通块
	}
	for(int i=1;i<=Tot;i++){
		for(int j=1;j<l[i].nin.size();j++){
			ans.pb(mk(l[i].nin[j-1],l[i].nin[j]));   //同连通块没有入度点相邻连接
		}
		for(int j=1;j<l[i].nout.size();j++){
			ans.pb(mk(l[i].nout[j-1],l[i].nout[j]));   //同连通块没有出度点相邻连接
		}
	}
	for(int i=2;i<=Tot;i++) ans.pb(mk(l[i-1].nout.back(),l[i].nin[0]));
	ans.pb(mk(l[Tot].nout.back(),l[1].nin[0]));   //相邻连通块连接
	cout<<ans.size()<<endl;
	for(auto i:ans) cout<<i.fi<<' '<<i.se<<endl;
	return 0;
}
posted @   Binary_Lee  阅读(20)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话
Title
点击右上角即可分享
微信分享提示