1. 程式人生 > 實用技巧 >學校網路(含證明)

學校網路(含證明)

歐拉回路的證明我都沒寫,這個我卻寫了,哎。

題目

題目

做法

第一問其實就是讓你求用強連通縮點之後入度為\(0\)的點。

不難證明的一件事情是,縮點之後是個\(DAG\),而\(DAG\)必然存在入度為\(0\)的點(如果不存在,你從一個點出發一直走指向你的邊,最後就會走成一個環。)。

入度為\(0\)的點是肯定需要放的,沒什麼好說的。但是單單放了入度為\(0\)的點就行了嗎?一個點一直走父親邊就可以到達入度為\(0\)的點。

第二問的話,設入度為\(0\)的點集為\(P\),出度為\(0\)的點集為\(Q\),那麼答案就是:\(max(|P|,|Q|)\)

驚人的發現我的證法是普通證法的複雜版。

但是為了偷懶直接寫普通證法算了

反正至少曾經我想到過證明

參照部落格:https://www.acwing.com/solution/content/4663/

首先,先說要實現最小邊數的條件。
我們知道一條有向邊可以貢獻一個入度,一個出度,相應的,也就可以消掉一個\(P\)的點和一個\(Q\)的點,所以至少要\(max(|P|,|Q|)\)

分類討論。

  1. \(|P|=1\),此時將\(Q\)中所有的點連向\(P\)即可,所用的邊的數量為\(|Q|\)
  2. \(|Q|=1\),此時將\(Q\)連向\(P\)中所有的點即可,所用的邊的數量為\(|P|\)
  3. 其餘的情況,我們只需要將\(Q\)中的一個點\(q_1\)連向\(P\)中的一個點\(p_2\)
    ,那麼這個時候指向\(q_1\)的的點就會全部指向\(p_2\)所指向的點,此時剛好把\(q_1\)\(p_2\)\(P,Q\)中毫無痕跡的刪除,然後重複此操作到刪除了\(min(|P|,|Q|)-1\)對點後,開始進入\(1,2\)操作。

這樣的話就證明了一定存在一種方案是\(max(|P|,|Q|)\)的。

兩者一結合,便證明此結論了。

當然,對於入度出度都為\(0\)的點,你可能會說連一條邊只會刪掉一個點,但是其實你看這個點啊,拆點,將出去的邊和進來的邊分開來,這樣在對外顯示上是沒有任何問題的,而且可以幫助你更形象的理解,當然你也可以更硬核的理解,就是\(P,Q\)中都有這個點,這個理解在程式碼時思路會更加清晰,但是結論就擺在那,入度出度都為\(0\)

的點並不會影響結論。

當然,如果整個圖就是一個分量的話,答案是\(0\),特判一下即可。

#include<cstdio>
#include<cstring>
#define  N  110
#define  M  110000
using  namespace  std;
inline  int  mymin(int  x,int  y){return  x<y?x:y;}
inline  int  mymax(int  x,int  y){return  x>y?x:y;}
struct  node
{
	int  y,next;
}a[M];int  len,last[N];
inline  void  ins(int  x,int  y){len++;a[len].y=y;a[len].next=last[x];last[x]=len;}
int  dfn[N],low[N],n,in[N]/*入度*/,out[N]/*出度*/,ti,be[N],sta[N],top,block;
void  dfs(int  x)
{
	sta[++top]=x;dfn[x]=low[x]=++ti;
	for(int  k=last[x];k;k=a[k].next)
	{
		int  y=a[k].y;
		if(!dfn[y])
		{
			dfs(y);
			low[x]=mymin(low[x],low[y]);
		}
		else  if(!be[y])low[x]=mymin(low[x],low[y]);
	}
	if(dfn[x]==low[x])
	{
		block++;
		while(sta[top]!=x)
		{
			be[sta[top--]]=block;
		}
		be[sta[top--]]=block;
	}
}
int  main()
{
	scanf("%d",&n);
	for(int  i=1;i<=n;i++)
	{
		int  x;
		while(1)
		{
			scanf("%d",&x);
			if(x==0)break;
			ins(i,x);
		}
	}
	for(int  i=1;i<=n;i++)if(!dfn[i])dfs(i);
	for(int  i=1;i<=n;i++)
	{
		for(int  k=last[i];k;k=a[k].next)
		{
			int  y=a[k].y;
			if(be[i]!=be[y])in[be[y]]++,out[be[i]]++;
		}
	}
	int  ans1=0,ans2=0;
	for(int  i=1;i<=block;i++)
	{
		if(!in[i])ans1++;
		if(!out[i])ans2++;
	}
	if(block==1)printf("1\n0\n");
	else  printf("%d\n%d\n",ans1,mymax(ans1,ans2));
	return  0;
}