首页 > 其他分享 >【题解】P3225 [HNOI2012]矿场搭建(割点,dfs)

【题解】P3225 [HNOI2012]矿场搭建(割点,dfs)

时间:2022-09-28 23:11:23浏览次数:77  
标签:连通 题解 割点 dfs 出口 叶子 maxn

【题解】P3225 [HNOI2012]矿场搭建

割点好题!

(因为刚开始没想清楚卡了好久/kk)

题目链接

P3225 [HNOI2012]矿场搭建

题意概述

给定一张 \(n\) 条边的无向图,现在要求在其中一些点上修建一些“出口”,使得断掉任意一个点之后,都能使得其它点可以到达至少一个“出口”。问最少要设置多少个出口,以及方案数是多少。

思路分析

这个其实很容易看出来要用割点。关键是怎么用。

首先由于这道题中没有告诉点的个数,我们假设点的个数为 \(m\),并将断掉所有割点后的图视为一个个连通块。

我们可以考虑根据图中割点个数进行分类讨论:

  • 当图中没有割点时,也就是图是一张点双连通图时。

    如果设置一个“出口”,那么对其它点无所谓,但若设置了出口的点断掉,那么其它点就不能到达任意一个出口,所以只能至少设置两个点。

    在点双中,每个点都是等价的。所以可以在 \(m\) 个点中任选两个作为“出口”,即方案数为 \(\mathrm C_m^2=\frac {m \times (m-1)}{2}\)。

  • 当图中有割点时:

    • 假如断的是割点:那么与这个割点相连的连通块中的点,要么这个连通块中本身有“出口”,要么这个连通块的点可以通过另一个与之相连的割点到达另一个连通块中,而到达的那个连通块中有“出口”。但对于只与一个割点相连的连通块,我们只能通过第一种方式到达“出口”,我们称这样的连通块为叶子连通块

      那么每一个叶子连通块中内部必须有一个“出口”,否则无法满足题意。那么当所有叶子连通块中都有一个出口时,对于非叶子连通块,那么就一定有一条路径使得这个连通块的节点能够到达其中一个叶子连通块,所以就不必在这些非叶子连通块中再设立“出口”。

      这里可能有点抽象,我们画个图理解一下:

      img

      如图,在这张图中,点 \(1,2,3\) 构成 ① 号的连通块和 \(4,6,7\) 构成的 ③ 号连通块都分别只与割点 \(1\) 和割点 \(4\) 相连,都是叶子连通块。而由于点 \(1,4,5\) 构成的 ② 号连通块与 \(1,4\) 两个割点相连,所以它不是叶子连通块。

      那么假如当 \(1\) 或 \(4\) 断开时,①/③ 号连通块的点都不能通过其它方式到达 ② 号连通块,所以必须要在它们两个连通块内部设置两个“出口”。

      而对于 ② 号连通块,不管是断了 \(1\) 还是断了 \(4\),它都可以通过另一个割点到达另一个连通块中,所以无需在这个连通块中设置“出口”。

      所以这种情况下,就直接在每一个叶子连通块中设立一个出口就好了。

      那么答案就为整张图中叶子连通块的个数,根据乘法原理,方案数就为每个叶子联通块的大小乘积。

    • 假如断开的不是割点:那么对于任意一个连通块中的点,都可以通过割点到达另一个连通块中去,也就是说,我们只需要设置一个“出口”即可。

    综上,当图中有割点时,我们可以只考虑在叶子连通块中设置“出口”。

    那么现在问题就转化为,如何求出图中有多少个叶子连通块,以及每个叶子连通块的大小是多少。

    比较好想的一种思路是:

    首先第一遍 dfs 找出所有割点并把它们标记出来。

    第二遍枚举所有的点,如果这个点不是割点并且没有被访问过则从这个点开始 dfs。

    dfs 的过程中将走过的所有点标记为已访问,假设当前遍历到的点是 \(x\),那么遍历到 \(x\) 时首先 \(vis_x++\),然后对于与 \(x\) 连边的所有点 \(y\),当且仅当 \(y\) 没有被访问过且 \(y\) 不是割点时继续 dfs \(y\) 这个点。

    除此之外,我们还要判断当前连通块是否为叶子连通块

    根据叶子连通块的定义,我们可以考虑在 dfs 的过程中维护当前连通块与几个割点相连。我们用一个 \(book\) 数组来表示每个割点是否被 dfs 到过,对于割点 \(t\),若 dfs 到过则 \(book=1\),反之则为 \(0\)。并用一个变量 \(num\) 表示有多少个割点与当前连通块相连。

    延续上面的 \(x\),当我们在遍历到与 \(x\) 连边的所有 \(y\) 时,如果 \(y\) 是割点且 \(book_y=0\) 则 \(num++\) 并将 \(book_y\) 标记为 \(1\)。

    这样对于遍历到的每个连通块,若 dfs 结束后 \(num=1\) 则说明它是叶子连通块。

    那么如何求出每个连通块的大小呢?

    我们可以考虑维护一个数组 \(sz_x\) 表示以 \(x\) 为根的子树大小。

    那么我们在枚举所有点求叶子连通块时,假如枚举到一个符合要求点 \(x\) 开始 dfs,最后的答案 \(sz_x\) 就是 \(x\) 所在连通块的大小。

    现在问题就转化为如何求出 \(sz_x\),其实这很简单:

    \[\]

    \[ \]

    易错点

    • 本题中有多个变量和数组,且数据多测,请不要忘了清空数组。
    • 我一开始一直有一个错误的思路就是对于有割点的情况,答案是连通块个数,并没有考虑到对于一个非叶子连通块来说,其中一个割点断了还可以通过另一个割点“逃出去”。所以要仔细思考,注意题目的关键条件。

    代码实现

    //luoguP3225
    //10:41
    #include<cstdio>
    #include<iostream>
    #include<cstring>
    #include<algorithm>
    #define pii pair<int,int>
    #define mk make_pair
    #define int long long
    #define debug
    using namespace std;
    const int maxn=1005;
    int dfn[maxn],low[maxn],sz[maxn],ok[maxn],cnt,tot,rt,num,now;
    int vis[maxn],book[maxn][maxn];
    
    basic_string<pii>edge[maxn];
    
    inline int read()
    {
    	int x=0,f=1;char ch=getchar();
    	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    	while(ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
    	return x*f;
    }
    
    void dfs(int x,int pre)
    {
    	dfn[x]=low[x]=++tot;
    	int ret=0;
    	for(auto nxt:edge[x])
    	{
    		int y=nxt.first,id=nxt.second;
    		if(!dfn[y])
    		{
    			dfs(y,id);
    			low[x]=min(low[x],low[y]);
    			if(low[y]>=dfn[x])ret++;
    		}
    		else if(id!=pre)low[x]=min(low[x],dfn[y]);
    	}
    	if(ret>=2||(ret==1&&pre!=0))ok[x]++,rt++;
    	return ;
    }
    
    void dfs2(int x,int fa)
    {
    #ifdef debug2
    	cout<<x<<" "<<fa<<endl;
    #endif
    	sz[x]=1;
    	vis[x]++;
    	for(auto nxt:edge[x])
    	{
    		int y=nxt.first;
    		if(y==fa||vis[y])continue;
    		if(ok[y])
    		{
    			if(book[now][y]==0){num++;book[now][y]++;}
    			continue;
    		}
    		dfs2(y,x);
    		sz[x]+=sz[y];
    	}
    }
    
    signed main()
    {
    	int T=0;
    	int n=read();
    	while(n)
    	{
    #ifdef debug2
    		cout<<"n="<<n<<endl;
    #endif
    		T++;
    		cout<<"Case "<<T<<": ";
    		for(int i=1;i<=n;i++)edge[i].clear();
    		memset(dfn,0,sizeof(dfn));
    		memset(low,0,sizeof(low));
    		memset(vis,0,sizeof(vis));
    		memset(ok,0,sizeof(ok));
    		memset(sz,0,sizeof(sz));
    		cnt=0;tot=0;rt=0;
    		int mx=0;
    		for(int i=1;i<=n;i++)
    		{
    			int u,v;
    			u=read();v=read();
    			edge[u]+=mk(v,i);
    			edge[v]+=mk(u,i);
    			mx=max({mx,u,v});
    		}
    		for(int i=1;i<=mx;i++)if(!dfn[i])dfs(i,0);
    		if(rt==0)
    		{
    			cout<<2<<" ";
    			cout<<mx*(mx-1)/2<<'\n';
    			n=read();
    			continue;
    		}
    //		cout<<cnt+1<<" ";
    		int ans=1;
    #ifdef debug2
    		cout<<cnt<<endl;
    #endif
    		for(int i=1;i<=mx;i++)
    		{
    #ifdef debug2
    			cout<<i<<" "<<ok[i]<<"\n";
    #endif
    			if(ok[i])continue;
    			if(!vis[i])
    			{
    				num=0;now=i;
    				dfs2(i,0);
    #ifdef debug2
    			    cout<<"i="<<i<<" "<<num<<'\n';
    #endif			    	
    				if(num>1)continue;
    				ans*=sz[i];
    				cnt++;
    			}
    		}
    		cout<<cnt<<" "<<ans<<'\n';
    		n=read();
    	}
    	return 0;
    }
    /*
      分类讨论一下:
      - 当图中没有割点时:答案为 2,方案数为 v*(v-1)/2。
      - 当图中有割点时,答案就是删掉割点后的连通块数量,方案数为每个"叶子"连通块的 sz 相乘。
     */
    

标签:连通,题解,割点,dfs,出口,叶子,maxn
From: https://www.cnblogs.com/xrkforces/p/luogu-P3225.html

相关文章

  • CF1182E 名字太长不想打 题解
    题解区都是用矩阵直接算封闭形式中\(f_1,f_2,f_3\)的系数的,这里给个更偏MO风格的做法。首先先想办法用\(f_x\cdotk(x)\)代\(f_x\)以消掉\(c^{2x+6}\)这个不好......
  • 【Python】【爬虫】【问题解决方案记录】调试输出存在数据,print在控制台确丢失数据
    如下图,调试可以看到数据是完整的但是print输出的,恰好丢失了中间的一大堆数据。对,下图打问号的地方应该是小说才对。看代码可能看不出缺失内容,可视化看看对吧,......
  • Mac M1 安装 Nacos 操作及问题解决
    nacos依赖mysql先安装mysql,这里使用的是8+版本,原因在于原本的5.7版本中并没有对m1的良好支持,如果启动会有报错说查询不到对应版本信息(虽然可以通过自定义mirror......
  • USACO 2022赛季 简要题解
    DECGOLD-A-PairedUpG有\(n\)只奶牛,第\(i\)只在位置\(x_i\),有重量\(y_i\)。求在满足匹配要求的情况下,非匹配的奶牛的重量之和的最大/最小值。两只奶牛能......
  • DFS
    原理原理很简单,就是搜索一条路,一路走到头;如果不符合题意,返回上一节点;再从上一节点找另一条子节点继续往下走;思路path[]该数组用来存储方法,当到达路径底部,返回该方案;......
  • DFS算法练习 POJ1111; POJ1129; POJ2245; POJ2657
    POJ1111:importjava.util.Scanner;/***@Authorjinjun99*@DateCreatedin2022/9/279:49*@Description*@Sinceversion-1.0*/publicclassMain{......
  • 移动端touch拖动事件和click事件冲突问题解决
    通过一个悬浮球交互功能的案例来阐述问题,以及解决办法。实现效果类似微信里的悬浮窗效果,苹果手机的悬浮球功能效果可以点击拖动,然后吸附在窗口边缘点击悬浮球,可......
  • 题解【CF1363F Rotating Substrings】
    CF1363FRotatingSubstrings*2600解题报告。不一定更好的阅读体验。感觉楼上的DP状态设计与DP转移方程的联系是说不通的,有些地方没有讲明白,所以这篇题解就详细......
  • CF1603D Artistic Partition 题解
    题意一个长度为\(n\)的序列,分为\(k\)段,每段代价是\(c(l,r)=\sum\limits_{i=l}^r\sum\limits_{j=i}^r[\gcd(i,j)\geql]\),求总代价的最小值。分析若\(2^k>n\),总......
  • Tomcat最全乱码问题解决方案(保姆教程)
    目录概述原因解决方法1.idea乱码和startup.bat启动控制台日志乱码(Tomcat日志乱码)2.浏览器乱码概述tomcat乱码问题相信大家肯定都遇见过,本篇将详细介绍有关Tomcat的各种......