首页 > 其他分享 >[HNOI2012] 矿场搭建 题解

[HNOI2012] 矿场搭建 题解

时间:2024-03-16 09:23:07浏览次数:25  
标签:连通 矿场 min 题解 割点 long HNOI2012 dfn low

[HNOI2012] 矿场搭建

前置知识: #Tarjan求点双连通分量

题目大意

​ 给你一张无向连通图,任意删去其中一个点,要求在删点后在每个连通块中有一个点被选择,问至少需要选择多少个点,以及选择的方案数。

输入输出格式

输入

​ 多组数据以\(N=0\)结束

​ 每组数据第一行为边的数量\(N \ (N \leq 500)\)

​ 接下来\(N\)行,每行两个整数\(S\)和\(T\),表示一条边连接的两个点。\((1\le S,T \le 10^3)\)

输出

​ 对于每组数据输出一行两个数,表示至少需要选择多少个点,以及选择的方案数。

样例输入

9
1 3
4 1
3 5
1 2
2 6
1 5
6 3
1 6
3 2
6
1 2
1 3
2 4
2 5
3 6
3 7
0

样例输出

Case 1: 2 4
Case 2: 4 1

思路

​ 删除一个点会使无向图中联通块发生变化的只有割点,而不受割点影响的只有点双连通分量,所以我们可以求出所有割点点双连通分量,再对每一个点双连通分量进行分类讨论

分类讨论:

\(Ⅰ\)

​ 当一个点双连通分量中没有割点时,就需要选择两个点以防其中的一个点被删除,因为不能从割点连通到另一
个点双连通分量。

对最少选择的点数\(ans1\)的贡献为\(2\),对选择的方案\(ans2\)的贡献为\(ans2 \cdot \frac{size*(size-1)}{2} \ (size为点双连通分量的大小)\)

\(Ⅱ\)

​ 当一个点双连通分量中有一个割点时,只需要选择除割点外的任一点,因为如果删除割点,这个点双连通分量中有一个点被选择,如果删除我们选择的那个点,我们可以从割点连通另一个点双连通分量。

对最少选择的点数\(ans1\)的贡献为\(1\),对选择的方案\(ans2\)的贡献为\(ans2 \cdot size \ (size为点双连通分量的大小)\)

\(Ⅲ\)

​ 当一个点双连通分量中有两个及以上割点时,无论删除哪一个割点,我们可以从剩下的割点连通另外的点双连通分量。

对最少选择的点数\(ans1\)的贡献为\(0\),对选择的方案\(ans2\)的贡献为\(ans2 \times 1\)

输入

9
1 3
4 1
3 5
1 2
2 6
1 5
6 3
1 6
3 2

输出

Case 1: 2 4

(提问时间 3-5min)

\(Code\)

#include<bits/stdc++.h>
using namespace std;
inline long long read(){
    long long x=0,w=0;char c=0;
    while(!isdigit(c)) {w|=c=='-';c=getchar();}
    while(isdigit(c)) x=(x<<3)+(x<<1)+(c^48),c=getchar();
    return w?-x:x;
}
long long T,n,h[1050],t;
struct node{
	long long to;
	long long nex;
}e[1050];
inline void add(long long x,long long y){
	e[++t].to=y;
	e[t].nex=h[x];
	h[x]=t;
}
long long dfn[1050],low[1050],cnt,num,ans1,ans2;
bool vis[1050],flag[1050];
stack<long long> st;
vector<long long> dcc[1050];
void tj(long long x){
	dfn[x]=low[x]=++num;
	vis[x]=1;
	st.push(x);
	long long son=0;
	for(long long i=h[x];i;i=e[i].nex){
		long long v=e[i].to;
		if(!dfn[v]){
			tj(v);
			low[x]=min(low[x],low[v]);
			if(low[v]>=dfn[x]){
				son++;
				if(x!=1||son>1){
					flag[x]=1;
				}
				cnt++;
				long long y;
				do{
					y=st.top();
					st.pop();
					dcc[cnt].push_back(y);
				}while(y!=v);
				dcc[cnt].push_back(x);
			}
		}
		else{
			low[x]=min(low[x],dfn[v]);
		}
	}
}//求点双连通分量和割点
int main(){
	while(1){
		n=read();
		if(n==0){
			break;
		}
		T++;
		num=cnt=ans1=t=0;
		ans2=1;
		memset(h,0,sizeof(h));
		memset(dfn,0,sizeof(dfn));
		memset(low,0,sizeof(low));
		memset(vis,0,sizeof(vis));
		memset(flag,0,sizeof(flag));
		for(long long i=1;i<=1010;i++){
			dcc[i].clear();
		}
		while(st.size()){
			st.pop();
		}
		for(long long i=1,x,y;i<=n;i++){
			x=read();
			y=read();
			add(x,y);
			add(y,x);
		}
		tj(1);
		for(int i=1;i<=cnt;i++){
			int s1=dcc[i].size(),s2=0;
			for(int j=0;j<s1;j++){
				s2+=flag[dcc[i][j]];
			}
			if(s2==1){//分类讨论Ⅱ
				ans1++;
				ans2*=(s1-1);
			}
			if(s2==0){//分类讨论Ⅰ
				ans1+=2;
				ans2*=(s1*(s1-1)/2);
			}
		}
		printf("Case %lld: %lld %lld\n",T,ans1,ans2);
	}
	return 0;
}

\(Warning\)

因为讲完后有同学对代码的一些地方不理解,所以在这里对其正确性进行证明。

聚焦代码

void tj(long long x){
	dfn[x]=low[x]=++num;
	vis[x]=1;
	st.push(x);
	long long son=0;
	for(long long i=h[x];i;i=e[i].nex){
		long long v=e[i].to;
		if(!dfn[v]){
			tj(v);
			low[x]=min(low[x],low[v]);
			if(low[v]>=dfn[x]){
				son++;
				if(x!=1||son>1){
					flag[x]=1;
				}
				cnt++;
				long long y;
				do{
					y=st.top();
					st.pop();
					dcc[cnt].push_back(y);
				}while(y!=v);
				dcc[cnt].push_back(x);
			}
		}
		else{
			low[x]=min(low[x],dfn[v]);
		}
	}
}

因为正常的 \(tarjan\) 和 \(树\) 的遍历都会判掉\(father\)节点

void tj(long long x,long long fa){//change
	dfn[x]=low[x]=++num;
	vis[x]=1;
	st.push(x);
	long long son=0;
	for(long long i=h[x];i;i=e[i].nex){
		long long v=e[i].to;
		if(v==fa){
			continue;
		}//change
		if(!dfn[v]){
			tj(v);
			low[x]=min(low[x],low[v]);
			if(low[v]>=dfn[x]){
				son++;
				if(x!=1||son>1){
					flag[x]=1;
				}
				cnt++;
				long long y;
				do{
					y=st.top();
					st.pop();
					dcc[cnt].push_back(y);
				}while(y!=v);
				dcc[cnt].push_back(x);
			}
		}
		else{
			low[x]=min(low[x],dfn[v]);
		}
	}
}

但不判父节点的写法其正确性是可以保证的。

\(Part \ 1\)

else{
	low[x]=min(low[x],dfn[v]);
}

首先这里并不会递归到它的父亲节点,所以不会出现反复横跳的情况,时间复杂度得到了保证。

\(Part \ 2\)

if(!dfn[v]){
	tj(v);
	low[x]=min(low[x],low[v]);
	if(low[v]>=dfn[x]){
		..............
	}
}

因为\(tarjan\)算法由递归实现,所以一个节点必然是先求出它的 \(dfn \ 和 \ low 值\) 然后再影响它的父亲进行割点判断。

那么对于 \(low[x]=min(low[x],dfn[v]);(v为x的father时)\)

有两种情况

\(Ⅰ\)

\(low[x]\) 从其它的 \(v\) 里转移来的值小于等于 \(dfn[v](v为x的father)\)

此时 \(low[x]=min(low[x],dfn[v]);(v为x的father时)\) 可以忽略

\(Ⅱ\)

\(low[x]\) 从其它的 \(v\) 里转移来的值大于 \(dfn[v](v为x的father)\)

此时 \(low[x]=min(low[x],dfn[v]);(v为x的father时)\) ,那么\(low[x]=dfn[v]\)

此时我们回溯到上一层(就是 \(x\) 的 \(father\) ),由于判断时是 \(low[v]>=dfn[x]\) 所以还是会进入\(if内部语句\)。

所以不会对答案产生影响

总结

综上所述,不判 \(father\) 节点不会出现问题。

而且,按照我们的推理,\(low[x]\) 只会小于或等于 \(dfn[fa]\) ,因为 \(low[x]\) 比 \(dfn[fa]\) 大,语句\(low[x]=min(low[x],dfn[v])\)就会起效。

所以我们将 \(if(low[v]>=dfn[x])\) 改为 \(if(low[v]==dfn[x])\) 也不会出问题。

提交记录

标签:连通,矿场,min,题解,割点,long,HNOI2012,dfn,low
From: https://www.cnblogs.com/pjt-camellia/p/18076702

相关文章

  • NOI2021 轻重边 题解
    NOI2021轻重边题目链接:#4812.[NOI2021]轻重边前置知识:#树链剖分#线段树题目大意给定\(n\)个点组成的树,\(m\)次操作。修改操作会让路径\(a\tob\)上的所有点的所有连边对答案的贡献变为\(0\),路径\(a\tob\)的所有边的贡献变为\(1\);查询操作则统计路径\(a\tob\)的......
  • CF57C Array 题解
    发现单调不降序列反过来就是单调不增序列,只需考虑单调不降序列即可。假如将问题转化为:初始为\(1\),一共有\(n+1\)个位置,有\(n-1\)次增加答案的机会,每个位置可以拥有多次增加答案的机会,问一共有多少种可能性。显然答案为\(C_{2n-1}^{n-1}\)。所以总体答案为\(2C_{2n-1}^{n-......
  • P2824 [HEOI2016/TJOI2016] 排序 与 ABC297_g Range Sort Query 题解
    洛谷题目链接:排序abc题目链接:Atcoder或者洛谷两道差不多的题拿出来说说。本题有双\(\log\)做法,也有单\(\log\)做法,都讲讲。双\(\log\)做法对于第一个题而言,询问最终\(pos\)位置上的数为多少,那么这个问题是否具有单调性?这个是很有意思的点,我们考虑只关注某个数\(x\)......
  • 洛谷题解 - B3850 [GESP202306 四级] 幸运数
    目录题目描述输入格式输出格式样例#1样例输入#1样例输出#1代码题目描述小明发明了一种“幸运数”。一个正整数,其偶数位不变(个位为第111位,十位为第......
  • 炸弹题解
    这题有两种做法,一种tarjan,一种逆天DP用lower_bound或upper查找i所在范围的左右边界对应下标普通Tarjan+缩点#include<bits/stdc++.h>#defineintlonglongusingnamespacestd;constintN=5e5+10,mod=1e9+7;intn,dfn[N],low[N],head2[N],num,cnt,head[N],belong[N];b......
  • 【PR #12】划分序列 / Yet Another Mex Problem 题解
    题目链接题目大意给定一个长度为\(n\)的序列\(a\),定义一段区间的价值为该区间的\(\operatorname{mex}\)乘上区间元素总和。你需要将序列划分成若干个长度\(\leqk\)的区间。一个划分方案的价值为划分出来的每个区间价值之和,求所有划分方案的价值最大值。\(1\leqk\le......
  • centos6使用yum网络源失败,问题解决
    在进行测试环境部署时,需要用到yum安装一些软件包,目前服务器是通外网的,所以这里我就直接使用的网络源进行yum下载的令我惊讶的是用yum命令安装居然失败了!!!以下是我的排查到解决的心路历程:1.首先执行命令yumlist查看发现报错如下:从报错信息来看是说无法连接到http(s),ftp的......
  • CF575H Bots 题解 组合数学
    Bots传送门SashaandIraaretwobestfriends.Buttheyaren’tjustfriends,theyaresoftwareengineersandexpertsinartificialintelligence.Theyaredevelopinganalgorithmfortwobotsplayingatwo-playergame.Thegameiscooperativeandturn......
  • 常见问题解决 --- idea与maven使用常识
    1.拿到项目代码后先要知道使用了哪些技术和工具。比如使用的是idea、eclipse还是maven创建的项目,使用什么编程语言,使用什么项目目录结构等等2.如何用maven创建的项目必然有pom.xml,每次修改pom文件后必须重新加载。3.如果修改代码后还是报错,尝试使用clean清除编译缓存再同步maven......
  • Luna likes Love 题解
    ProblemLink简要题意题目很清楚。分析定理两个人中左边的人一直向右运动,和两人向中间走所用的步数相同证明假设有两组人为\(a_l,a_r,b_l,b_r(a_l<a_r,b_l<b_r)\)。\(\textrm{I}.\)当\(a_r<b_l\)(两者互不相交)时如图:显然成立。$\textrm{II}.$......