Day7 割点、割边和强连通分量

版权声明:https://blog.csdn.net/huashuimu2003 https://blog.csdn.net/huashuimu2003/article/details/87467182

A.【dfn】交换机 割点 模板

题目

题目描述
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.局部变量 a n s ans 未初始化为 0 0
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届 杨晓虎 岳川梦真

题解

首先,这个点一定是一个割点。
s t 然后我们可以考虑s与t必经的割点。
x y 3 如果有一个x点,他连接了一个y点,那么我们还可以总结出3个条件:
1. x 1.x不是起点或终点,因为题目要求在中间服务器上建嗅探器。
2. x 2.x是割点。
3. t d f n y d f n y 访 3.终点t的dfn应该大于等于y点的dfn,因为要确保终点在y点或之后被访问到,
x 即x点为必经的点。
4. t l o w x d f n x 4.终点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

错误原因

a n s [ ] ans[] 未开 l o n g l o n g long 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. 最受欢迎的牛 强连通分量+思维

题目p1233

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;
}

猜你喜欢

转载自blog.csdn.net/huashuimu2003/article/details/87467182
今日推荐