速通ACM省铜第二天 赋源码(Adjacent XOR和Arboris Contractio)

目录

引言:

[Adjacent XOR](#Adjacent XOR)

题意分析

逻辑梳理

代码实现

[Arboris Contractio](#Arboris Contractio)

题意分析

逻辑梳理

代码实现

结语:


引言:

本来今天也是打算讲一道题的,但是因为第一道的1400的题感觉有点过于简单了,感觉没有1400的题的感觉,于是就决定这篇讲俩题,而且好巧不巧,这俩题竟然是在同一场div3里的,而且更神奇的是,E题开出来的人竟然比D题还多,如图

但确实,我打下来也感觉D题比E题难打一点,那么接下来,我们就开始进行题目的讲解------------>

​​​​​​​ ​​​​​​​ ​​​​​​​ ​​​​​​​


Adjacent XOR

那么,我们先来讲相对另一道而言总体偏简单的题目,我们先来分析一下题目

题意分析

这是题目链接Problem - 2131E - Codeforces

不想跳转可看下图

首先,我们来看题目,这个题目的描述十分的简单,就是给你一个数组a,然后问你能不能把这个数组a变成数组b

然后操作也很简单,就是可以随便选择下标(需要没被选择过的),然后对该下标和相邻的下一个下标的俩个值进行异或

题目的意思就这么简单,那么我们来将题目的要求进行逻辑梳理一番


逻辑梳理

首先,题目问我们a能不能转换成b,最简单的一个判断方式就是判断这俩个数组的最末尾元素是否一样,若不一样,则输出"NO",若一样,再进行讨论,为什么呢,我们来结合我画的图进行分析

根据题意,a数组的变化,撑死只能将最末尾的i与i+1进行变化,但这样变化后得到的新元素的位置也在i位,但是这种已经是最靠后的变化方式了,所以,我们可知,a数组的最后一位,是无法变化的,所以如果a跟b数组的末尾元素不一样,那么,a就不能转换为b

那么若a与b数组的末尾元素是一样的,接下来,我们来进行下一步的分析

首先,如果我们判断能否将a[i]变化成b[i],我们可以有俩种方式,一种是a[i]与b[i]是否一样,另一种是a[i]异或a[i+1]后是否与b[i]一样,不管是哪种情况,i 位置的能否转换都不会被 i 前面a数组的元素所影响,所以我们可以思考倒推,因为只有后面的元素可以对a[i]产生影响

因为题目告诉了我们,选择异或的顺序是可以任意的,所以当选择 i 这个位置进行判断时,会有三种情况

1.a[i]是否等于b[i]

2.a[i]^a[i+1]是否等于b[i]

3.a[i]^b[i+1]是否等于b[i]

只有这三个条件都无法满足时,才会输出"NO",那么我们具体来讲解下这三种情况

第一种情况很简单,相等的话不用操作就是符合的,很简单

第二种情况也很简单,就是 i+1 下标上的元素并没有选择进行异或,先对下标为 i 的位置进行异或,若等于,就可以变化(因为 i 在 i+1 前,所以 i+1 之后想要变化时候也不会被 i 的变化所影响)

第三种情况也好想,b[i+1]表示的是a[i+1]已经被进行异或过了,然后通过i与已经被异或过的i+1进行异或,若可以变化,则可以实现目的

所以,只有这三种情况都不满足时,才会使a数组无法变成b数组

那么,这个题目的思路已经理清楚了,那么代码也自然就很简单了,那么接下来,我们来写代码


代码实现

这个题主要是逻辑难想,逻辑想通了代码其实很简单,那么,这边就直接放AC的代码了

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <iostream>
#include <string.h>
#include <algorithm>
#include <math.h>
#include <queue>
using namespace std;

int t, n;
long long a[200010];
long long b[200010];

int main()
{
	cin >> t;
	while (t--)
	{
		cin >> n;
		for (int i = 1; i <= n; i++)
			cin >> a[i];
		for (int i = 1; i<=n; i++)
			cin >> b[i];
		if (a[n] != b[n])
		{
			cout << "No" << endl;
			continue;
		}
		int xixi = 1;
		int j = n - 1;
		while (j)
		{
			if (a[j] != b[j] && b[j] != (a[j] ^ b[j + 1]) && b[j] != (a[j] ^ a[j + 1]))
			{
				xixi = 0;
				cout << "No" << endl;
				break;
			}
			j--;
		}
		if (xixi)
			cout << "Yes" << endl;
	}
	return 0;
}

那么,这题就讲到这里结束啦,希望你们有所收获

接下来,我们来讲下一道题,下一道题跟上一道题刚好相反,思路好想,但是代码比较难实现


Arboris Contractio

那么,我们先来分析一下题目

题意分析

这是题目链接Problem - 2131D - Codeforces

不想跳转可看下图

这题目相比上一题看着挺长的,但其实问的也很简单,就是题目会给你一个无环数,然后通过一系列的方法,将那颗无环树的深度变为2,然后问你最少的操作次数是几次,就没了

然后一次操作分为三步走,就是选一条路,然后把那条路所有的点全部拆开,然后再将后面的点全部跟头的那个点连上,就是一次操作

那么接下来,我们进行逻辑分析


逻辑梳理

逻辑其实很好梳理,首先,对于每个点的最少操作次数只需要数该节点深度不为1的分支有几条,分支的结果就是他需要操作的最小次数(这个自己理会,应该是很好理解的),然后通过一个个的对比,找出操作次数最少的一个节点,然后输出即可

思路是很清晰的,但是明显,这个思路是完全不可能实现的,因为若要实现这个思路,首先每个节点都要访问一遍,每次访问不同节点的时候,其他节点对于该节点的深度也会发生变化,而且每个节点的分支也不同,极多的不可控因素会使得这个思路完全无法转换成代码来实现,这也是困住了我挺久的地方

所以我们需要通过原先的思路,来创造出一个能转换成代码的思路

那么,既然是分支,我们就可以思考到叶子节点,因为每个点的分支,肯定最后都会抵达叶子节点,所以我们可以先算出这颗树总共有多少个叶子结点,然后一个循环,遍历完所有的点,每次遍历一个点的时候,可以将叶子节点的总数减去该节点已经有的深度为1的叶子节点的个数,这样得到的值便是该点的最少操作次数,这么一来,是不是就比一开始的时候的方式便捷了许多

:我们可以进行特判,即若只有俩个点,那输出便为0,因为2个点不管怎么连都已经是最小宽度了,根本不需要进行任何操作

那么,逻辑梳理就梳理完了,接下来就进入代码实现部分


代码实现

在讲代码实现之前,特别注意,一定要把数组提到外面去,不然会导致栈溢出,切记,这点卡了我好久,结果发现原来是数组开在局部把栈区挤爆了(因为我一开始把数组放局部了,导致一运行就异常)那么,数组放在全局了,就不要忘了每次循环完进行清空操作

除开这点,别的就很简单了,我们用vector来存储每个点所相连接的点,然后通过判断点的size来确定该点是不是叶子结点,因为叶子节点的size肯定为1

然后我们想让总次数最少,只需要点的叶子结点最多即可,因为一个点的叶子节点越多,减去的值就越大,故而总次数越少

那么,接下来,我们就来看AC代码

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <iostream>
#include <string.h>
#include <algorithm>
#include <math.h>
#include <queue>
#include <vector>
using namespace std;

int t, n;
vector<int> arr[200010];
void solv()
{
	int u, v;
	cin >> n;
	for (int i = 1; i < n; i++)
	{
		cin >> u >> v;
		arr[u].push_back(v);
		arr[v].push_back(u);
	}
	if (n == 2)
	{
		cout << "0" << endl;
		return;
	}
	int yesum = 0;
	for (int i = 1; i <= n; i++)
		if (arr[i].size() == 1)
			yesum++;
	int ye = 0;
	for (int i = 1; i <= n; i++)
	{
		int Ye = 0;
		for (int j = 0; j < arr[i].size(); j++)
		{
			if (arr[arr[i][j]].size() == 1)
				Ye++;
		}
		ye = max(ye, Ye);
	}
	cout << yesum - ye << endl;
	for (int i = 1; i <= n; i++)
	{
		while (arr[i].size())
			arr[i].clear();
	}
}

int main()
{
	cin >> t;
	while (t--)
	{
		solv();
	}
	return 0;
}

那么,这题也讲完啦

结语:

今日算法讲解到此结束啦,希望对你们有所帮助,谢谢观看,如果觉得不错可以分享给朋友哟

相关推荐
zzzsde3 小时前
【c++】类和对象(4)
开发语言·c++
抓饼先生3 小时前
C++ 20 视图view笔记
linux·开发语言·c++·笔记·c++20
大可门耳3 小时前
qt调用cef的Demo,实现js与C++之间的交互细节
javascript·c++·经验分享·qt
半桔3 小时前
【STL源码剖析】二叉世界的平衡:从BST 到 AVL-tree 和 RB-tree 的插入逻辑
java·数据结构·c++·算法·set·map
R_.L4 小时前
【项目】 :C++ - 仿mudou库one thread one loop式并发服务器实现(代码实现)
服务器·开发语言·c++
R_.L4 小时前
【项目】 :C++ - 仿mudou库one thread one loop式并发服务器实现(模块划分)
服务器·c++
塔中妖4 小时前
【华为OD】分割数组的最大差值
数据结构·算法·华为od
weixin_307779135 小时前
最小曲面问题的欧拉-拉格朗日方程 / 曲面极值问题的变分法推导
算法
RTC老炮5 小时前
webrtc弱网-AlrDetector类源码分析与算法原理
服务器·网络·算法·php·webrtc