CF22E Scheme

題目鏈接

題意分析

對於每一個點i 都指向一個點fi 從而形成一個有向圖

請問至少添加多少條邊 可以將原圖變成一個強連通圖 並輸出任一方案


啥是強連通圖

對於一個有向圖D 如果任意點vi,vj且vi≠vj

滿足從vi到vj 從vj到vi 都存在一條路徑使得其連通 那麼有向圖D就是強連通圖


首先 我們可以明白的 這個有向圖中的強連通分量中的點之間肯定是不用再連邊了

所以我們使用Tarjan縮點 這樣就得到了若干個DAG

同時 由於這道題中的一個點只存在一個出度 所以一個DAG中的有向邊替換成無向邊之後就是成爲了一棵樹

所以 從某種意義上 我們得到了一片森林

接下來就是怎麼連邊了

首先 樹和樹之間肯定是要連接的 我們將一棵樹看成一個點 也就是將其連成一個環

具體到一棵樹上的話 我們讓葉子節點 也就是入度爲零的點 順次相連

無標題.png

這樣連接的話 我們發現仍然存在一個入讀爲零的點

如果森林中只用一棵樹的話 我們讓這棵樹的根節點與那個入度爲零的葉子節點相連

無標題.png

如果森林中存在多棵樹的話 我們讓這棵樹的根節點與下一棵樹的入度爲零的葉子節點相連

無標題.png

至於統計數量的話 就是樹的數量+Σ(每一棵樹中葉子節點的數量-1)

具體的實現細節可以看代碼

CODE:

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#define M 100861
using namespace std;
int n,tot,cnt,top;
int edge[M],to[M],nex[M],head[M];
int dfn[M],low[M],sta[M];
int bel[M],belt[M],wt[M];
bool vis[M];
int in[M],out[M];
vector<int> have[M],leaf[M],root[M];
void add(int x,int y)
{to[++tot]=y;nex[tot]=head[x];head[x]=tot;}
void Tarjan(int now)
{
//	printf("now is at %d\n",now);
	dfn[now]=low[now]=++cnt;sta[++top]=now;vis[now]=1;
	for(int i=head[now];i;i=nex[i])
	{
		int v=to[i];
		if(!dfn[v]) {Tarjan(v);low[now]=min(low[now],low[v]);}
		else if(vis[v]) {low[now]=min(low[now],dfn[v]);}
		
	}
	if(dfn[now]==low[now])
	{
		++tot;
		while(sta[top+1]!=now)
		{
			bel[sta[top]]=tot;
			vis[sta[top]]=0;
			have[tot].push_back(sta[top]);//我是用vector存儲每一個強連通分量裏面的點 
			--top;
		}
	}
}
int find(int x)
{return x==belt[x] ? x:belt[x]=find(belt[x]);}
void merge(int x,int y)
{
	int fx=find(x),fy=find(y);
	if(fx!=fy) belt[fx]=fy;
}
int main()
{
	scanf("%d",&n);
	
	for(int i=1,x;i<=n;++i)
	{
		scanf("%d",&x);edge[i]=x;
		
		add(i,x);
	}
	tot=0;
	for(int i=n;i;--i) if(!dfn[i]) Tarjan(i);//使用Tarjan來縮點 
	if(tot==1) 
	{//如果縮點之後只存在一個點的話  那麼這已經是強連通圖了 
		printf("0");
		return 0;
	}
	for(int i=1;i<=tot;++i) belt[i]=i;//我使用並查集來區分每一棵樹 
	for(int i=1;i<=n;++i)
	{
		if(bel[i]!=bel[edge[i]])
		{//統計縮完點之後每一個縮點對應的入度以及出度 
			merge(bel[i],bel[edge[i]]);
			in[bel[edge[i]]]++;
			out[bel[i]]++;
		}
	}
	cnt=0;
	int tmp=0;
	for(int i=1;i<=tot;++i)
	{//現在使用vector存儲每一棵樹的葉子節點以及根節點 
		int fx=find(i);
		if(!wt[fx]) wt[fx]=++cnt;
		if(in[i]==0) root[wt[fx]].push_back(i);
		if(out[i]==0) leaf[wt[fx]].push_back(i); 
	}
	for(int i=1;i<=cnt;++i) tmp+=(int)root[i].size()-1;
	printf("%d\n",cnt+tmp);
	for(int i=1;i<=cnt;++i)
	{
		if(i<cnt) printf("%d %d\n",have[leaf[i][0]][0],have[root[i+1][0]][0]);
		else printf("%d %d\n",have[leaf[cnt][0]][0],have[root[1][0]][0]);
	}
	for(int i=1;i<=cnt;++i)
	{
		if((int)root[i].size()<2) continue;
		for(int j=0;j<(int)root[i].size()-1;++j)
		printf("%d %d\n",have[root[i][j]][0],have[root[i][j+1]][0]);
	}
	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章