Parity Game——种类并查集、权值并查集、离散化

题目描述

思路

怎么得到这个序列中每一段的关系?

  • 我们可以把这个只包含0和1的序列看作一个数组,0表示当前位置为0,1表示当前位置为1,利用前缀和的性质可以知道某一段中所包含的1的数量
  • sum1 = a[r] - a[l-1]
  1. 如果sum1为偶数,那么a[r] 和 a[l-1]的奇偶性相同
  2. 如果sum1为奇数,那么a[r] 和 a[l-1]的奇偶性不同
  • 找到它们之间的关系,我们就可以使用并查集来存储他们

为什么要离散化?

  • 序列的长度小于等于1e9,然而序列中下标出现的次数最多为1e4,所以使用离散化

种类并查集

  • 序列中某一段有两种关系,奇数个1、偶数个1
  • 我们定义两个扩展域,1~n表示偶数,n + 1 ~ 2 * n表示奇数
  • 每次知道一个关系之后,将偶数区域和奇数区域都进行处理,像枚举一样,不漏掉每一种情况
  • 并查集中存储的是区域与区域之间的关系,如果有一个关系成立,那么这个集合中其它的关系都成立

代码实现

#include <iostream>
#include <algorithm>
#include <vector>

using namespace std;

const int N = 1e4 + 10;

int fa[2 * N];		// 两个扩展域:1~N表示偶数关系,N+1~2N表示奇数关系
int n, m;
vector<int> ans;	// 离散化数组

struct node	// 结构体,存储每一段区间的左右端点
{
	int x, y;
	string op;
}a[N];

int get(int u)	// 二分查找,将原数据离散化成下标
{
	int l = 0, r = ans.size();
	while(l + 1 != r)
	{
		int mid = l + r >> 1;
		if(ans[mid] < u) l = mid;
		else r = mid;
	}
	return r;
}

int find(int u)		// 返回当前元素的祖宗元素,在哪个集合中
{
	if(fa[u] != u) fa[u] = find(fa[u]);
	return fa[u];
}

void merge(int x, int y)	// 将x合并到y所在的集合中
{
	fa[find(x)] = find(y);
}

int main()
{
	cin >> n >> m;
	for(int i = 1; i <= m; i++)
	{
		cin >> a[i].x >> a[i].y >> a[i].op;	
		a[i].x--;	// 因为利用了前缀和的性质,所以要将左端点-1,满足sum = a[r] - a[l-1]
		ans.push_back(a[i].x);
		ans.push_back(a[i].y);
	}
	sort(ans.begin(), ans.end());	// 将数据进行排序
	ans.erase(unique(ans.begin(), ans.end()), ans.end());	// 进行去重
	ans.insert(ans.begin(), 0);	// 将离散化之后的下标从1开始
	for(int i = 1;i <= m; i++)	// 找到每个区间左右端点离散化之后的数据,使用新数据
	{
		a[i].x = get(a[i].x);
		a[i].y = get(a[i].y);
	}
	for(int i = 0;i < 2 * N; i++) fa[i] = i;	// 初始化集合,每一个元素在不同的集合中
	n = ans.size();	// 每个扩展域的范围
	for(int i = 1; i <= m; i++)	// 开始遍历每一个回答,判断左右端点集合中的关系
	{
		int x = a[i].x, y = a[i].y;
		string op = a[i].op;
		if(op == "even")	// 有偶数个1
		{
            // 只需要判断一种情况就可,因为他们的关系是对称的
			if(find(x) == find(y + n))	// x为偶数的集合中,存在y为奇数的关系,矛盾
			{
				cout << i - 1 << endl;
				return 0;
			}
            // x与y的奇偶性相同
			merge(x, y); 	// 将x为偶数,y为偶数放在一个集合中
			merge(x + n, y + n);	// 将x为奇数,y为奇数放在一个集合中
		}
		else	// 有奇数个1,那么x和y的奇偶性不同
		{
			if(find(x) == find(y))	// x为偶数的集合中,存在y为偶数的关系,矛盾
			{
				cout << i - 1 << endl;
				return 0;
			}
			merge(x, y + n);	// 将x为偶数,y为奇数放在一个集合中
			merge(x + n, y);	// 将x为奇数,y为偶数放在一个集合中
		}
	}
	cout << m << endl;
	
	return 0;
}

权值并查集

  • 每一个区间的奇偶性有两种情况,奇数或者偶数
  • 我们可以维护集合中每个区间的关系,这个关系可以用集合中的子区间到祖宗区间的距离来表示,因为有两种情况,所以当距离d[i] % 2 == 0时,当前区间的奇偶性和祖宗元素的奇偶性相同,d[i] % 2 ==1时,当前区间的奇偶性和祖宗元素的奇偶性不同
  • 每一个集合中的区间相对于祖宗区间的奇偶性是已知的,所以集合中所有区间的奇偶性关系都是已知的

代码实现

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

const int N = 1e5 + 10;

int fa[N];	// 初始化每一个集合都是独立的
int d[N];	// 集合中子区间到祖宗区间的距离,可以判断奇偶关系
vector<int> ans;	// 离散化数组
int n, m;

struct node	// 存储每一个回答
{
	int x, y;
	string op;
}a[N];

int get(int u)	// 二分查找进行离散化
{
	int l = 0, r = ans.size();
	while(l + 1 != r)
	{
		int mid = l + r >> 1;
		if(ans[mid] < u) l = mid;
		else r = mid;
	}
	return r;
}

int find(int u)	// 找到当前区间的祖宗区间,并进行路径压缩和更新到祖宗区间的距离
{
	if(fa[u] != u)
	{
		int t = find(fa[u]);
		d[u] += d[fa[u]];
		fa[u] = t;
	}
	return fa[u];
}

int main()
{
	cin >> n >> m;
	for(int i = 1;i <= m; i++)
	{
		cin >> a[i].x >> a[i].y >> a[i].op;
		a[i].x--;
		ans.push_back(a[i].x);
		ans.push_back(a[i].y);
	}
	sort(ans.begin(), ans.end());
	ans.erase(unique(ans.begin(), ans.end()), ans.end());
	ans.insert(ans.begin(), 0);
	for(int i = 1; i <= m; i++) 
	{
		a[i].x = get(a[i].x);
		a[i].y = get(a[i].y);
	}
	for(int i = 1; i <= ans.size(); i++) fa[i] = i;
	for(int i = 1;i <= m; i++)
	{
		int fx = find(a[i].x), fy = find(a[i].y);
		string op = a[i].op;
		if(op == "even")
		{
			if(fx != fy)
			{
				d[fx] = d[a[i].x] ^ d[a[i].y];
				fa[fx] = fy;
			}
			else 
			{
				if((d[a[i].x] + d[a[i].y]) % 2 != 0)
				{
					cout << i - 1 << endl;
					return 0;
				}
			}
		}
		else
		{
			if(fx != fy)
			{
				d[fx] = d[a[i].x] ^ d[a[i].y] ^ 1;
				fa[fx] = fy;
			}
			else
			{
				if((d[a[i].x] + d[a[i].y]) % 2 == 0)
				{
					cout << i - 1 << endl;
					
					return 0;
				}
			}
		}
	}
	cout << m << endl;
	
	return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/165972.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Rust开发——切片(slice)类型

1、什么是切片 在 Rust 中&#xff0c;切片&#xff08;slice&#xff09;是一种基本类型和序列类型。在 Rust 官方文档中&#xff0c;切片被定义为“对连续序列的动态大小视图”。 但在rust的Github 源码中切片被定义如下&#xff1a; 切片是对一块内存的视图&#xff0c;表…

如何隐藏Selenium特征实现自动化网页采集

Selenium是一个流行的自动化网页测试工具&#xff0c;可以通过模拟用户在Chrome浏览器中的操作来完成网站的测试。然而&#xff0c;有些网站会检测浏览器是否由Selenium驱动&#xff0c;如果是&#xff0c;就会返回错误的结果或拒绝访问。为了避免这种情况&#xff0c;我们需要…

多线程编程

1 线程的使用 1.1 为什么要使用多线程 在编写代码时&#xff0c;是否会遇到以下的场景会感觉到难以下手&#xff1f; 要做 2 件事&#xff0c;一件需要阻塞等待&#xff0c;另一件需要实时进行。例如播放器&#xff1a;一边在屏幕上播放视频&#xff0c;一边在等待用户的按…

中间件安全:Apache 目录穿透.(CVE-2021-41773)

中间件安全&#xff1a;Apache 目录穿透.&#xff08;CVE-2021-41773&#xff09; Apache 的 2.4.49、2.4.50 版本 对路径规范化所做的更改中存在一个路径穿越漏洞&#xff0c;攻击者可利用该漏洞读取到Web目录外的其他文件&#xff0c;如系统配置文件、网站源码等&#xff0c…

Polygon zkEVM协议治理、升级及其流程

1. 引言 随着Polygon社区开发者和内部团队的测试深入&#xff0c;当前版本的Polygon zkEVM不可避免地需更新和某些升级。 为激励开发者对Polygon zkEVM做battle-test&#xff0c;已启动了bug-bounty&#xff1a; Rewards by Threat Level 由于zk-Rollup生态系统还处于萌芽阶…

算法设计与分析复习--贪心(二)

文章目录 上一篇哈夫曼编码单源最短路最小生成树Kruskal算法Prim算法 多机调度问题下一篇 上一篇 算法设计与分析复习–贪心&#xff08;一&#xff09; 哈夫曼编码 产生这种前缀码的方式称为哈夫曼树 哈夫曼树相关习题AcWing 148. 合并果子 #include <iostream> #inc…

LDO线性稳压器要不要并联二极管?

昨天介绍过了LDO是什么东西&#xff0c;那么对于它的应用场景是怎么的呢&#xff1f;LDO要不要并联二极管呢&#xff1f; 一般来说&#xff0c;LDO是不需要并联二极管的。 看下图第一个是典型电路&#xff0c;第二个是带可调节电压功能的LDO典型电路&#xff0c;从图里就可以…

设计模式-组合模式-笔记

“数据结构”模式 常常有一些组件在内部具有特定的数据结构&#xff0c;如果让客户程序依赖这些特定数据结构&#xff0c;将极大地破坏组件的复用。这时候&#xff0c;将这些特定数据结构封装在内部&#xff0c;在外部提供统一的接口&#xff0c;来实现与特定数据结构无关的访…

一起Talk Android吧(第五百五十四回:分享一个Retorfit使用错误的案例)

文章目录 1. 案例场景2. 案例现象3. 原因分析和解决方案3.1 原因分析3.2 解决方案4. 经验总结各位看官们大家好,上一回中咱们说的例子是"解析Retrofit返回的数据",本章回中将分享一个 Retrofit使用错误的案例。闲话休提,言归正转,让我们一起Talk Android吧! 1. …

三层交换机实现不同VLAN间通讯

默认时&#xff0c;同一个VLAN中的主机才能彼此通信&#xff0c;那么交换机上的VLAN用户之间如何通信&#xff1f; 要实现VLAN之间用户的通信&#xff0c;就必须借助路由器或三层交换机来完成。 下面以三层交换机为例子说明&#xff1a; 注意&#xff1a; 1.交换机与三层交换…

HWS-CTF-第七期山大站-inverse

文章目录 inversemainworkread_intread_n 思路onegadget exp 第一次真正意义上独立在比赛中做出题目来了&#xff0c;距离真正意义接触CTF-PWN差不多正好两个月。但由于不知道靶场要自己开而且端口每次自己打开会改&#xff0c;交flag稍微晚了些&#xff08;我太菜了&#xff0…

Java中锁的深入理解

目录 对象头的理解 Monitor&#xff08;锁&#xff09; 锁类型 偏向锁 偏向锁的优化机制 轻量级锁 重量级锁 对象头的理解 在32位Java虚拟机中普通对象的对象头是占用8个字节&#xff0c;其中4个字节为Mark Word。用来存储对象的哈希值&#xff0c;对象创建后在JVM中的…

【顺序表的实现】

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 目录 前言 1. 数据结构相关概念 1、什么是数据结构 2、为什么需要数据结构&#xff1f; 2、顺序表 1、顺序表的概念及结构 1.1 线性表 2、顺序表分类 3、动态顺序表的实现 总…

ssm+vue的高校疫情防控管理系统(有报告)。Javaee项目,ssm vue前后端分离项目。

演示视频&#xff1a; ssmvue的高校疫情防控管理系统&#xff08;有报告&#xff09;。Javaee项目&#xff0c;ssm vue前后端分离项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结…

【C++入门】拷贝构造运算符重载

目录 1. 拷贝构造函数 1.1 概念 1.2 特征 1.3 常用场景 2. 赋值运算符重载 2.1 运算符重载 2.2 特征 2.3 赋值运算符 前言 拷贝构造和运算符重载是面向对象编程中至关重要的部分&#xff0c;它们C编程中的一个核心领域&#xff0c;本期我详细的介绍拷贝构造和运算符重载。 1. …

面向对象与面向过程的区别

面向对象 以对象为中心&#xff0c;把数据封装成为一个整体&#xff0c;其他数据无法直接修改它的数据&#xff0c;将问题分解成不同对象&#xff0c;然后给予对象相应的属性和行为。 面向过程 关注代码过程&#xff0c;直接一程序来处理数据&#xff0c;各模块之间有调用与…

OSI参考模型

目录 一. OSI参考模型的各层功能二. 网络排错三. 网络安全四. 实体、协议、服务和服务访问点SAP五. TCP IP体系结构 一. OSI参考模型的各层功能 \quad \quad \quad \quad 我们首先来看应用层实现的功能 每个字段的各种取值所代表的意思 \quad \quad 比如要保存的文件内容是ab…

OpenAI 董事会与 Sam Altman 讨论重返 CEO 岗位事宜

The Verge 援引多位知情人士消息称&#xff0c;OpenAI 董事会正在与 Sam Altman 讨论他重新担任首席执行官的可能性。 有一位知情人士表示&#xff0c;Altman 对于回归公司一事的态度暧昧&#xff0c;尤其是在他没有任何提前通知的情况下被解雇后。他希望对公司的治理模式进行重…

【开源】基于Vue.js的高校实验室管理系统的设计和实现

项目编号&#xff1a; S 015 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S015&#xff0c;文末获取源码。} 项目编号&#xff1a;S015&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、研究内容2.1 实验室类型模块2.2 实验室模块2.3 实…

Tomcat无法映射到activiti-app导致activiti无法启动页面

原因之一&#xff1a;JDK版本与Tomcat版本不匹配&#xff0c;jdk8 yyds 我使用的是JDK11&#xff0c;Tomcat是9.0的&#xff0c;都是最新的&#xff0c;但还是不行&#xff0c;最后JDK改为8&#xff0c;tomcat的cmd后台没有报错&#xff0c;activiti-pp也可以正常访问了,很神奇…