速通ACM省铜第二天 赋源码(Adjacent XOR和Arboris Contractio)
目录
引言:
Adjacent XOR
题意分析
逻辑梳理
代码实现
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的代码了
#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代码
#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;
}
那么,这题也讲完啦
结语:
今日算法讲解到此结束啦,希望对你们有所帮助,谢谢观看,如果觉得不错可以分享给朋友哟