当前位置: 首页 > news >正文

信息学奥赛一本通 1861:【10NOIP提高组】关押罪犯 | 洛谷 P1525 [NOIP 2010 提高组] 关押罪犯

【题目链接】

ybt 1861:【10NOIP提高组】关押罪犯
洛谷 P1525 [NOIP 2010 提高组] 关押罪犯

【题目考点】

1. 图论:二分图
2. 二分答案
3. 种类并查集

【解题思路】

解法1:种类并查集

一个囚犯是一个顶点,一个囚犯对可以看做一条边,每个囚犯对的怨气值是边的权值。
一个监狱的囚犯属于一个集合,也就是一个连通分量。
现在给定很多顶点和预设的边,将所有的顶点分为两个集合,每个集合中顶点之间的边都生效。求所有的集合划分方案中集合中最大边权的最小值。

为了使集合中边权最大的边最小,可以将所有边按权值从大到小排序。按权值从大到小遍历所有的边,尽量让权值大的边所连接的两个顶点不在同一个集合。如果发现某条边连接的两个顶点无法放在不同的集合,那么该边的两个顶点就必须放在相同的集合中,该边就是集合中边权最大的边。该边的边权就是所有的集合划分方案中集合中最大边权的最小值。

对于第 i i i个顶点,我们可以假想存在顶点 i + n i+n i+n,顶点 i + n i+n i+n与顶点 i i i不在同一个集合。
对于权值为 w w w的边 ( u , v ) (u, v) (u,v)

  • 如果顶点 u u u和顶点 v v v已经在同一集合,那么已经无法将 u u u v v v放到两个不同的集合,集合中存在的最大边权就是 w w w w w w就是该问题的结果。

  • 否则,让顶点 u u u和顶点 v v v不在同一个集合。由于顶点 v + n v+n v+n和顶点 v v v不在同一集合,那么顶点 u u u和顶点 v + n v+n v+n一定在同一集合,将二者所在的集合合并。
    同理,顶点 v v v和顶点 u + n u+n u+n在同一集合,将二者所在的集合合并。

设排序后,有顶点1到顶点2的边,权值为3。有顶点1到顶点3的边,权值为3,有顶点2到顶点3的边,权值为2,设总顶点数n为10。
运行上述算法

  1. 访问顶点1到顶点2的边,将顶点1和假想的与顶点2不在同一集合的顶点12合并,顶点2和假想的与顶点1不在同一集合的顶点11合并。
  2. 访问顶点1到顶点3的边,将顶点1和顶点13合并,此时1, 12, 13处于同一集合。将订单3和顶点11合并,此时2, 3, 11处于同一集合。
  3. 访问顶点2到顶点3的边,发现顶点2、3在同一集合,那么集合中最大边权为顶点2到顶点3的边的权值,为2。

解法2:二分答案+二分图判定

市长看到的冲突事件是影响力最大的冲突事件。要使影响力最大的冲突事件的影响力最小,是最大值最小问题,可以使用二分答案解决。

  • 答案变量:集合中的最大边权c
  • 最值:求最小值
  • 满足条件:将所有顶点分为两个集合,集合内顶点间的边权都小于等于c
    该条件仍然难以判断。可以反向思考,如果集合内顶点间的边权都小于等于c,那么边权大于c的边一定只能是分别连接两集合中的两个顶点。
    如果只考虑边权大于c的边,所有的边的一端是一个集合中的顶点,另一端是另一个集合中的顶点,该图就是一个二分图。
    因此二分答案满足条件为:只考虑边权大于c的边时,该图是二分图。
    可以使用染色法进行二分图判定,只有边权大于c时才通过该边访问邻接点。

可以直接二分答案边权数值。也可以先对保存边权的序列进行排序,而后在边权序列上进行二分查找目标边权的下标。

【题解代码】

解法1:种类并查集
#include<bits/stdc++.h>
using namespace std;
#define N 20005
#define M 100005
struct Edge
{
	int u, v, w;
	bool operator < (const Edge &b) const
	{
		return w > b.w;
	}
} e[M];
int n, m, fa[2*N], ans;
void initFa(int n)
{
	for(int i = 1; i <= 2*n; ++i)
		fa[i] = i;
}
int find(int x)
{
	return fa[x] = x == fa[x] ? x : find(fa[x]);
}
void merge(int x, int y)
{
	fa[find(x)] = find(y);
}
int main()
{
	cin >> n >> m;
	initFa(n);
	for(int i = 1; i <= m; ++i)
		cin >> e[i].u >> e[i].v >> e[i].w;
	sort(e+1, e+1+m);
	for(int i = 1; i <= m; ++i)
	{
		int u = e[i].u, v = e[i].v, w = e[i].w;
		if(find(u) == find(v))
		{
			ans = w;
			break;
		}
		merge(u, v+n);
		merge(v, u+n);
	}
	cout << ans;
	return 0;
}
解法2:二分答案+二分图判定
  • 写法1:广搜二分图判定,二分答案边权数值
#include<bits/stdc++.h>
using namespace std;
#define N 20005
struct Edge
{
	int v, w;
};
int n, m, color[N];
vector<Edge> edge[N];
bool bfs(int sv, int c)//二分图判定,只关注大于c的边
{
	queue<int> que;
	color[sv] = 1;
	que.push(sv);
	while(!que.empty())
	{
		int u = que.front();
		que.pop();
		for(Edge e : edge[u]) if(e.w > c)//只访问权值大于c的边 
		{
			int v = e.v;
			if(color[v] == 0)
			{
				color[v] = 3-color[u];
				que.push(v);
			}
			else if(color[v] == color[u])
				return false;
		}
	}
	return true;
}
bool check(int c)//只关注大于c的边时,该图是否为二分图
{
	memset(color, 0, sizeof(color));
	for(int i = 1; i <= n; ++i) if(color[i] == 0 && !bfs(i, c))
		return false;
	return true;
} 
int main()
{
	int a, b, c;
	cin >> n >> m;
	for(int i = 1; i <= m; ++i)
	{
		cin >> a >> b >> c;
		edge[a].push_back(Edge{b, c});
		edge[b].push_back(Edge{a, c});
	}
	int l = 0, r = 1e9;
	while(l < r)
	{
		int mid = (l+r)/2;
		if(check(mid))
			r = mid;
		else
			l = mid+1;
	}
	cout << l;
	return 0;
}
  • 写法2:深搜二分图判定,二分查找下标
#include<bits/stdc++.h>
using namespace std;
#define N 20005
#define M 100005
struct Edge
{
	int v, w;
};
int n, m, color[N], w[M];
vector<Edge> edge[N];
bool dfs(int u, int c)
{
	for(Edge e : edge[u]) if(e.w > c)
	{
		int v = e.v;
		if(color[v] == 0)
		{
			color[v] = 3-color[u];
			if(!dfs(v, c))
				return false;
		}
		else if(color[v] == color[u])
			return false;
	}
	return true;
}
bool check(int c)//只关注大于c的边时,该图是否为二分图
{
	memset(color, 0, sizeof(color));
	for(int i = 1; i <= n; ++i) if(color[i] == 0)
	{
		color[i] = 1;
		if(!dfs(i, c))
			return false;
	}
	return true;
} 
int main()
{
	int a, b, c;
	cin >> n >> m;
	for(int i = 1; i <= m; ++i)
	{
		cin >> a >> b >> c;
		edge[a].push_back(Edge{b, c});
		edge[b].push_back(Edge{a, c});
		w[i] = c;
	}
	sort(w+1, w+1+m);
	int l = 0, r = m;//l最小应该为0,w[0]为0,当下标取到0时,结果为0,此时取所有的边,仍然是二分图。 
	while(l <= r)
	{
		int mid = (l+r)/2;
		if(check(w[mid]))
			r = mid-1;
		else
			l = mid+1;
	}
	cout << w[l];
	return 0;
}

相关文章:

  • 软考系统架构设计师之大数据与人工智能笔记
  • [ctfshow web入门] web17
  • 力扣HOT100之链表:19. 删除链表的倒数第 N 个结点
  • 【MySQL 数据库】数据表的操作
  • 永磁同步电机无速度算法--基于HOPLL的滑模观测器
  • Python设计模式:组合模式
  • JavaScript基础--22-call、apply 和 bind
  • PPP实验笔记
  • C#语言的饼图
  • java.io快读快写StreamTokenizer、PrintWriter
  • 低级错误 System.setProperty 值为空时引发空指针
  • ubuntu,react的学习(1)
  • PandaAI:一个基于AI的对话式数据分析工具
  • 2025 年前端与后端开发方向的抉择与展望-优雅草卓伊凡
  • C++: 类型转换
  • 构建k8s下Helm私有仓库与自定义Chart开发指南
  • Workbench运动副与dyna对应关系(一)
  • Linux 实验
  • SQL并行产生进程数量问题
  • 使用sass 实现px转vh或vw,适配适老化时,在设计图字体大小的基础上,增加3px(可配置),
  • 电脑做网站服务器WIN7 买个域名/适合发朋友圈的营销广告
  • 个体户能做网站备案吗/网络推广营销策划方案
  • 微信网站开发哪家好/seo刷点击软件
  • wordpress自定义链接怎么配置/seo优化关键词排名
  • 武汉网站开发网站/百度网络推广怎么做
  • 用什么做网站更快捷方便/自己怎么做引流推广