Day7 割點、割邊和強連通分量

~hsm~發表於2019-02-16

膜你抄

膜你抄

tarjan陪伴強聯通分量
生成樹完成後思路才閃光
尤拉跑過的七橋古塘
讓你 心馳神往————《膜你抄》

A.【dfn】交換機 割點 模板

題目p1230

題目描述
n個城市之間有通訊網路,每個城市都有通訊交換機,直接或間接與其它城市連線。因電子裝置容易損壞,需給通訊點配備備用交換機。
但備用 交換機數量有限,不能全部配備,只能給部分重要城市配置。
於是規定:如果某個城市由於交換機損壞,不僅本城市通訊中斷,還造成其它城市通訊中斷,則配備備 用交換機。
請你根據城市線路情況,計算需配備備用交換機的城市個數,及需配備備用交換機城市的編號。
友情提示:圖論常見的坑點,重邊,自環,還有對本題來說的不連通
輸入格式
第一行,一個整數n,表示共有n個城市(2<=n<=20000)
下面有若干行(<=60000):每行2個數a、b,a、b是城市編號,表示a與b之間有直接通訊線路。
輸出格式
第一行,1個整數m,表示需m個備用交換機。
下面有m行,每行有一個整數,表示需配備交換機的城市編號。
輸出順序按編號由小到大。如果沒有城市需配備備用交換機則輸出0。
樣例資料
input
7
1 2
2 3
2 4
3 4
4 5
4 6
4 7
5 6
6 7
output
2
2
4
資料規模與約定
gdoi
時間限制:1s
空間限制:256MB

錯誤原因

1.區域性變數ansans未初始化為00
2.資料範圍開小了。

程式碼

#include<bits/stdc++.h>
using namespace std;
const int maxn=6e4+10;
template<typename T>inline void read(T &x)
{
	x=0;
	T f=1,ch=getchar();
	while (!isdigit(ch)) ch=getchar();
	if (ch=='-') f=-1, ch=getchar();
	while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
	x*=f;
}
int ver[maxn<<1],Next[maxn<<1],head[maxn],len;
inline void add(int x,int y)
{
	ver[++len]=y,Next[len]=head[x],head[x]=len;
}
int dfn[maxn],low[maxn],id,root;
bool cut[maxn];
inline void tarjan(int x)
{
	int tot=0;
	low[x]=dfn[x]=++id;
	for (int i=head[x];i;i=Next[i])
	{
		int y=ver[i];
		if (!dfn[y])
		{
			tarjan(y);
			low[x]=min(low[x],low[y]);//更新當前節點的low值
			if (low[y]>=dfn[x])
			{
				++tot;
				if (x^root || tot>1) cut[x]=1;
			}
		}
		else
			low[x]=min(low[x],dfn[y]);
	}
}
int main()
{
	freopen("gd.in","r",stdin);
	freopen("gd.out","w",stdout);
	int n,x,y,ans=0;read(n);
	while (scanf("%d %d",&x,&y)==2)
	{
		if (x==y) continue;
		add(x,y),add(y,x);
	}
	for (int i=1;i<=n;++i)
		if (!dfn[i]) root=i,tarjan(i);
	for (int i=1;i<=n;++i)
		if (cut[i]) ++ans;
	printf("%d\n",ans);//割點的總數
	for (int i=1;i<=n;++i)
		if (cut[i])
			printf("%d\n",i);//輸出割點
	return 0;
}

B. [zjoi2004]嗅探器——dfnlow經典題目 割點

題目p1693

LUOGU 5058

描述 Description
某軍搞資訊對抗實戰演習.紅軍成功地侵入了藍軍的內部網路.藍軍共有兩個資訊中心.紅軍計劃在某臺中間伺服器上安裝一個嗅探器,從而能夠偵聽到兩個資訊中心互相交換的所有資訊.但是藍軍的網路相當的龐大,資料包從一個資訊中心傳到另一個資訊中心可以不止有一條通路.現在需要你儘快地解決這個問題.應該把嗅探器安裝在哪個中間伺服器上才能保證所有的資料包都能被捕獲?
輸入格式 Input Format
第一行一個整數n(1<=n<=100000),表示藍軍網路中伺服器的數目.
接下來若干行是對藍軍網路的拓撲結構描述.每行是兩個整數i,j表示編號為I和編號為j的兩臺伺服器間存在連線(顯然連線是雙向的).伺服器的編號從1開始.描述一兩個0結束.再接下來一行是兩個整數a,b分別表示兩個中心伺服器的編號.
輸出格式 Output Format
如果有多個解輸出編號最小的一個.如果找不到任何解,輸出”No solution”.
樣例輸入 Sample Input
5
2 1
2 5
1 4
5 3
2 3
5 1
0 0
4 2
樣例輸出 Sample Output
1
時間限制 Time Limitation
1s
註釋 Hint
部分小資料
來源 Source
zjoi2004
資料加強來自 2017屆 楊曉虎 嶽川夢真

題解

首先,這個點一定是一個割點。
st然後我們可以考慮s與t必經的割點。
xy3如果有一個x點,他連線了一個y點,那麼我們還可以總結出3個條件:
1.x1.x不是起點或終點,因為題目要求在中間伺服器上建嗅探器。
2.x2.x是割點。
3.tdfnydfny3.終點t的dfn應該大於等於y點的dfn,因為要確保終點在y點或之後被訪問到,
x即x點為必經的點。
4.tlowxdfnx4.終點t的low應該大於等於x點的dfn,因為要確保終點必須要經過x點。

程式碼

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+10;
template<typename T>inline void read(T &x)
{
	x=0;
	T f=1,ch=getchar();
	while (!isdigit(ch)) ch=getchar();
	if (ch=='-') f=-1, ch=getchar();
	while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
	x*=f;
}
int ver[maxn<<1],Next[maxn<<1],head[maxn],len;
inline void add(int x,int y)
{
	ver[++len]=y,Next[len]=head[x],head[x]=len;
}
int s,t,ans=0x3f3f3f3f;
int dfn[maxn],low[maxn],id;
inline void tarjan(int x,int root)
{
	dfn[x]=low[x]=++id;
	for (int i=head[x];i;i=Next[i])
	{
		int y=ver[i];
		if (!dfn[y])
		{
			tarjan(y,x);
			low[x]=min(low[x],low[y]);
			if (x!=s && low[y]>=dfn[x] && dfn[t]>=dfn[y] && low[t]>=dfn[x])
				ans=min(ans,x);
		}
		else if (y!=root)
			low[x]=min(low[x],dfn[y]);
	}
}
int main()
{
	freopen("dfnIn.in","r",stdin);
	freopen("dfnin.out","w",stdout);
	int n;read(n);
	while (1)
	{
		int x,y;
		read(x);read(y);
		if (!x && !y) break;
		add(x,y),add(y,x);
	}
	read(s);read(t);
	tarjan(s,0);
	if (ans>n) printf("No solution\n");
	else printf("%d\n",ans);
	return 0;
}

C. BZOJ 1123: [POI2008]BLO 割點

題目

BZOJ 1123
LUOGU 3469

錯誤原因

ans[]ans[]未開longlonglong long

程式碼

#include<bits/stdc++.h>
using namespace std;
const int maxn=5e5+10;
template<typename T>inline void read(T &x)
{
	x=0;
	T f=1,ch=getchar();
	while (!isdigit(ch)) ch=getchar();
	if (ch=='-') f=-1, ch=getchar();
	while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
	x*=f;
}
int ver[maxn<<1],Next[maxn<<1],head[maxn],len;
inline void add(int x,int y)
{
	ver[++len]=y,Next[len]=head[x],head[x]=len;
}
int dfn[maxn],low[maxn],id,n,m;
int cut[maxn],siz[maxn];
long long ans[maxn];
inline void tarjan(int x)
{
	dfn[x]=low[x]=++id;
	siz[x]=1;
	int tot=0,sum=0;
	for (int i=head[x];i;i=Next[i])
	{
		int y=ver[i];
		if (!dfn[y])
		{
			tarjan(y);
			siz[x]+=siz[y];//計算以x為跟的子樹的大小
			low[x]=min(low[x],low[y]);
			if (low[y]>=dfn[x])//割點判定法則
			{
				++tot;
				ans[x]+=(long long)siz[y]*(n-siz[y]);
				sum+=siz[y];
				if (x!=1 || tot>1) cut[x]=1;//1為根節點
			}
		}
		else
			low[x]=min(low[x],dfn[y]);
	}
	if (cut[x])
		ans[x]+=(long long)(n-1-sum)*(1+sum)+(n-1);
	else
		ans[x]=(n-1)<<1;//不是割點,此時答案為2*(n-1)
}
int main()
{
	freopen("test.in","r",stdin);
	freopen("test.out","w",stdout);
	read(n);read(m);len=1;
	for (int i=1;i<=m;++i)
	{
		int x,y;
		read(x);read(y);
		if (x==y) continue;
		add(x,y);add(y,x);
	}
	tarjan(1);
	for (int i=1;i<=n;++i)
		printf("%lld\n",ans[i]);
	return 0;
}

D. 危險道路 割邊

題目p1231

描述 Description
晗神是蘇聯的總書記。蘇聯有n個城市,某些城市之間修築了公路。任意兩個城市都可以通過公路直接或者間接到達。
晗神發現有些公路被毀壞之後會造成某兩個城市之間無法互相通過公路到達。這樣的公路就被稱為dangerous pavement。
為了防止美帝國對dangerous pavement進行轟炸,造成某些城市的地面運輸中斷,晗神決定在所有的dangerous pavement駐紮重兵。可是到底哪些是dangerous pavement呢?你的任務就是找出所有這樣的公路。
輸入格式 Input Format
第一行n,m(1<=n<=150, 1<=m<=5000),分別表示有n個城市,總共m條公路。
以下m行每行兩個整數a, b,表示城市a和城市b之間修築了直接的公路。
輸出格式 Output Format
輸出有若干行。每行包含兩個數字a,b(a<b),表示<a,b>是dangerous pavement。請注意:輸出時,所有的數對<a,b>必須按照a從小到大排序輸出;如果a相同,則根據b從小到大排序。
樣例輸入 Sample Input
6 6
1 2
2 3
2 4
3 5
4 5
5 6
樣例輸出 Sample Output
1 2
5 6
時間限制 Time Limitation
1s

錯誤原因

未知,原始碼在其他OJ可過,但是在HLOJ上不可過(加了檔案輸入輸出),已經重構程式碼,A掉!

程式碼

#include<bits/stdc++.h>
using namespace std;
const int maxn=3e5+10;
template<typename T>inline void read(T &x)
{
	x=0;
	T f=1,ch=getchar();
	while (!isdigit(ch)) ch=getchar();
	if (ch=='-') f=-1, ch=getchar();
	while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
	x*=f;
}
struct rec
{
	int y,next;
}cut[maxn<<1];
int ver[maxn<<1],Next[maxn<<1],head[maxn],len;
inline void add(int x,int y)
{
	ver[++len]=y,Next[len]=head[x],head[x]=len;
}
int dfn[maxn],low[maxn],id;
bool bridge[maxn<<1];
inline void Tarjan(int x,int inedge)
{
	dfn[x]=low[x]=++id;
	for (int i=head[x];i;i=Next[i])
	{
		int y=ver[i];
		if (!dfn[y])
		{
			Tarjan(y,i);
			low[x]=min(low[x],low[y]);
			if (low[y]>dfn[x])//此處無=號!!
				bridge[i]=bridge[i^1]=1;
		}
		else if (i!=(inedge^1))
			low[x]=min(low[x],dfn[y]);
	}
}
inline bool cmp(rec a,rec b)
{
	return (a.y<b.y)||(a.y==b.y&&a.next<b.next);
}
int main()
{
	freopen("danger.in","r",stdin);
	freopen("danger.out","w",stdout);
	int n,m;read(n);read(m);
	len=1;
	for (int i=1;i<=m;++i)
	{
		int a,b;
		read(a);read(b);
		add(a,b);add(b,a);
	}
	for (int i=1;i<=n;++i)
		if (!dfn[i]) Tarjan(i,0);
	int cnt=0;
	for (int i=2;i<=len;i+=2)
		if (bridge[i])
		{
			if (ver[i^1]<ver[i])
				cut[++cnt].y=ver[i^1],cut[cnt].next=ver[i];
			else
				cut[++cnt].y=ver[i],cut[cnt].next=ver[i^1];
		}
	sort(cut+1,cut+cnt+1,cmp);
	for (int i=1;i<=cnt;++i)
		printf("%d %d\n",cut[i].y,cut[i].next);
	return 0;
}

E. 上白澤慧音 最大強連通分量

題目P1726

LUOGU 1726

錯誤原因

輸出時末尾應該無空格。

程式碼

#include<bits/stdc++.h>
using namespace std;
const int maxn=5e5+10;
template<typename T>inline void read(T &x)
{
	x=0;
	T f=1,ch=getchar();
	while (!isdigit(ch)) ch=getchar();
	if (ch=='-') f=-1, ch=getchar();
	while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
	x*=f;
}
int ver[maxn<<1],Next[maxn<<1],head[maxn],len;
inline void add(int x,int y)
{
    ver[++len]=y,Next[len]=head[x],head[x]=len;
}
int dfn[maxn],low[maxn],id=0;
int belong[maxn],siz[maxn];
int Stack[maxn],top=0,tot=0;
int instack[maxn],ans[maxn];
inline void tarjan(int x)
{
    low[x]=dfn[x]=++id;
    instack[x]=1;
    Stack[++top]=x;
    for (int i=head[x];i;i=Next[i])
    {
        int y=ver[i];
        if (!dfn[y])
        {
            tarjan(y);
            low[x]=min(low[x],low[y]);
        }
        else if (instack[y])
            low[x]=min(low[x],dfn[y]);
    }
    if (low[x]==dfn[x])
    {
    	++tot;
		int k;
    	do
    	{
    		k=Stack[top--];
            ++siz[tot];
    		instack[k]=0;
    		belong[k]=tot;
        } while (k!=x);
    }
}
int maxnum=-1;
int main()
{
	freopen("classroom.in","r",stdin);
	freopen("classroom.out","w",stdout);
    int n,m;read(n);read(m);
    for (int i=1;i<=m;++i)
    {
        int x,y,z;
        read(x);read(y);read(z);
        add(x,y);
        if (z==2) add(y,x);
    }
    for (int i=1;i<=n;++i)
        if (!dfn[i]) tarjan(i);
    int dcc;
    for (int i=1;i<=n;++i)
        if (siz[belong[i]]>maxnum)
            maxnum=siz[belong[i]],dcc=i;
    printf("%d\n",maxnum);
    int cnt=0;
    for (int i=1;i<=n;++i)
        if (belong[i]==belong[dcc])
			ans[++cnt]=i;
	printf("%d",ans[1]);
	for (int i=2;i<=cnt;++i)
		printf(" %d",ans[i]);
    return 0;
}

F. BZOJ 1051: [HAOI2006]受歡迎的牛 強連通分量+思維

題目p1233

BZOJ 1051
LUOGU 2341

程式碼

#include<bits/stdc++.h>
using namespace std;
const int N=11000,M=51000;
template<typename T>inline void read(T &x)
{
	x=0;
	T f=1,ch=getchar();
	while (!isdigit(ch)) ch=getchar();
	if (ch=='-') f=-1, ch=getchar();
	while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
	x*=f;
}
int n,m,tot,a[M],b[M];
int dfn[N],low[N],id;
int ver[M],Next[M],head[M],len;
inline void insert(int x,int y)
{
	ver[++len]=y,Next[len]=head[x],head[x]=len;
}
int Stack[N],bel[N],size[N],mark[N],top;
bool instack[N];
inline void Tarjan(int x)
{
	dfn[x]=low[x]=++id;
	Stack[++top]=x;
	instack[x]=1;
	for (int i=head[x],y;i;i=Next[i])
	{
		if (!dfn[y=ver[i]])
		{
			Tarjan(y);
			low[x]=min(low[x],low[y]);
		}
		else if (instack[y])
			low[x]=min(low[x],dfn[y]);
	}

	if (dfn[x]==low[x])
	{
		int k; ++tot;
		do
		{
			k=Stack[top--];
			instack[k]=0;
			bel[k]=tot;
			++size[tot];
		} while (k!=x);
	}
}
int main()
{
	freopen("popular.in","r",stdin);
	freopen("popular.out","w",stdout);
	read(n);read(m);
	for (int i=1;i<=m;++i)
	{
		read(a[i]);read(b[i]);
		insert(a[i],b[i]);
	}
	for (int i=1;i<=n;++i)
		if (!dfn[i]) Tarjan(i);
	for (int i=1;i<=m;++i)
		if (bel[a[i]]!=bel[b[i]])
			++mark[bel[a[i]]];
	int ans=0;
	for (int i=1;i<=tot;++i)
		if (!mark[i])
		{
			if (ans)
			{
				printf("0\n");
				return 0;
			}
			ans=i;
		}
	printf("%d",size[ans]);
	return 0;
}

G. 殺人遊戲

題目

LUOGU 4819

Description
一位冷血的殺手潛入 Na-wiat,並假裝成平民。警察希望能在 N 個人裡面,查出誰是殺手。警察能夠對每一個人進行查證,假如查證的物件是平民,他會告訴警察,他認識的人, 誰是殺手, 誰是平民。
假如查證的物件是殺手, 殺手將會把警察幹掉。現在警察掌握了每一個人認識誰。每一個人都有可能是殺手,可看作他們是殺手的概率是相同的。
問:根據最優的情況,保證警察自身安全並知道誰是殺手的概率最大是多少?
Input
第一行有兩個整數 N,M。
接下來有 M 行,每行兩個整數 x,y,表示 x 認識 y(y 不一定認識 x) 。
Output
僅包含一行一個實數,保留小數點後面 6 位,表示最大概率。
Sample Input
5 4
1 2
1 3
1 4
1 5
Sample Output
0.800000
HINT
警察只需要查證 1。假如1是殺手,警察就會被殺。假如 1不是殺手,他會告訴警察 2,3,4,5 誰是殺手。
而 1 是殺手的概率是 0.2,所以能知道誰是殺手但沒被殺的概率是0.8。
對於 100%的資料有 1≤N ≤ 10 0000,0≤M ≤ 30 0000
資料已加強!

錯誤原因

for (int i=1; i<=tot; ++i)
{
	if (siz[i]==1 && !In[i] && !flag)//符合條件的“孤獨的人”
	{
		int truee=0;
		for (int j=head[i]; j; j=Next[j])//錯誤原因。。。。結果T掉
			if (In[ver[j]]==1)//如果它的入度比一大的話,證明不需要從這個點出發去查詢他
				truee=1;
		if (!truee) flag=1;
	}
	if (!In[i]) ++ans;
}

中的

for (int j=head[i];j;j=Next[j])//錯誤原因。。。。結果T掉

Next[j]中的j寫成了iij真的是很難辨識清楚啊!那我該怎麼解決啊,就把鄰接表迴圈的迴圈變數都寫成i,外圍迴圈寫成k

程式碼

#include<bits/stdc++.h>
using namespace std;
const int maxn=3e5+10;
template<typename T>inline void read(T &x)
{
    x=0;
    T f=1,ch=getchar();
    while (!isdigit(ch)) ch=getchar();
    if (ch=='-') f=-1, ch=getchar();
    while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
    x*=f;
}
int ver[maxn],Next[maxn],head[maxn],len;
inline void add(int x,int y)
{
    ver[++len]=y,Next[len]=head[x],head[x]=len;
}
int dfn[maxn],low[maxn],id;
int Stack[maxn],top,tot;
bool instack[maxn];
int belong[maxn],siz[maxn];
inline void tarjan(int x)
{
    dfn[x]=low[x]=++id;
    Stack[++top]=x;
    instack[x]=1;
    for (int i=head[x];i;i=Next[i])
    {
        int y=ver[i];
        if (!dfn[y])
        {
            tarjan(y);
            low[x]=min(low[x],low[y]);
        }
        else if (instack[y])
            low[x]=min(low[x],dfn[y]);
    }
    if (low[x]==dfn[x])
    {
        ++tot;
        int k;
        do
        {
            k=Stack[top--];
            instack[k]=0;
            belong[k]=tot;
            ++siz[tot];
        } while (k!=x);
    }
}
int a[maxn],b[maxn],In[maxn];
int main()
{
	freopen("test.in","r",stdin);
	freopen("test.out","w",stdout);
    int n,m;
    read(n);read(m);
    for (int i=1;i<=m;++i)
    {
        int x,y;
        read(x);read(y);
        if (x==y) continue;
        a[i]=x;b[i]=y;
        add(x,y);
    }
    for (int i=1;i<=n;++i)
        if (!dfn[i]) tarjan(i);
    memset(head,0,sizeof(head));
    len=0;
    for (int i=1;i<=m;++i)
        if (belong[a[i]]!=belong[b[i]])//屬於不同的連通塊
        {
            ++In[belong[b[i]]];//統計入度
            add(belong[a[i]],belong[b[i]]);//就將這兩個連通塊縮點連邊
        }
    int ans=0,flag=0;
    for (int k=1;k<=tot;++k)
    {
        if (siz[k]==1 && !In[k] && !flag)//符合條件的“孤獨的人”
        {
            int truee=0;
            for (int i=head[k];i;i=Next[i])//錯誤原因。。。。結果T掉,已經改正!
                if (In[ver[i]]==1)//如果它的入度比一大的話,證明不需要從這個點出發去查詢他
                    truee=1;
            if (!truee) flag=1;
        }
        if (!In[k]) ++ans;
    }
    if (flag) --ans;//如果這個人被標記了,就不用查詢了
    printf("%.6lf",1.0-(double)ans/(double)n);
    return 0;
}

相關文章