AcWing T1427 序列

AcWing T1427 序列

题目描述:

在这里插入图片描述
在这里插入图片描述

思路:

注意:由于本人实力不足,本题并不能AC,下面我会给出95分(19 / 20)的思路及代码。

注意到本题中有许多数据具有特殊性质的子任务,因此我们可以通过解决子任务来得分。
观察前两个子任务共十个测试点,它们都具有以下性质:
①每个序列只有两个数字;
②只有一个操作,且操作一定覆盖两个数字。
因此我们可以讨论一下t的两种情况,看一下怎么通过仅if/else语句完成这两个子任务。
(1)t = 1时,为了能够变形为b数组,由于a中的两个数同加同减,所以a中两个数与b中两个数的差值要相等。例如:
a : 1 5
b : 5 9
操作 : 1 1 2
在这个数据中,a中的两个数可以通过同时加1操作4次得到b数组,因此可以实现变形。
(2)t = 2时,我们可以这样理解:
如下图,先对a中两个数进行a[1]次操作二,这样一来,就变成了序列{0, a[2] + a[1]}要变形成为b数组的问题了。根据这个思路,我们
再让“0”的位置上加上b[1],“a[2] + a[1]”的位置上减去b[1],如果此时a[2] == b[2]的话,就可以变形成为b序列。即判断:
a[1] + a[2] - b[1] __ b[2]
即:a[1] + a[2] __ b[1] + b[2],若相等,则可以变形成为b,反之则不能。
在这里插入图片描述
根据上述分析,我们便可以写出解决前两个子任务的代码:

if(n == 2 && m == 1){
	if(t[1] == 1){
		if(a[1] - b[1] == a[2] - b[2]) puts("YES");
			else puts("NO");
	}
	else{
		if(a[1] + a[2] == b[1] + b[2]) puts("YES");
			else puts("NO");
	}
}

现在再来观察第三个子任务。这个子任务中的数据满足以下性质:
①每个序列只有两个数字;
②每个操作都一定会覆盖两个数字。
现在我们来看一下这种情况怎么解决。
设再经过k1次操作一和k2次操作二后,a序列变形成为了b序列。那么则有(假设操作一只做加法,操作二只会再a[2]上做加法,在a[1]上
做减法):
a[1] + k1 - k2 = b[1]
a[2] + k1 + k2 = b[2]
上式减去下式,得:
a[1] - a[2] - 2 * k2 = b[1] - b[2]
整理得:
a[1] - a[2] + b[2] - b[1] = -2 * k2
因此只需要判断等号左边的式子是不是偶数,即可判断出能否完成变形。
故我们可以写出解决这一个子任务的代码:

if((a[1] - a[2] + b[2] - b[1]) % 2 == 0) puts("YES");
	else puts("NO");

最后再来看一下第四个子任务。这个子任务中的数据满足以下性质:
①所有操作中只有操作二。
我们画一下图:

在这里插入图片描述在这里插入图片描述
如上图,在前面我们已经得出“操作二具有流动性”这一结论了,所以我们可以把所有用操作二的连接起来的点的权值放到一个点上。这样一来,将这些用操作二连接起来的点就形成了一个个连通块,而它们的权值之和存储在它们的父节点上。
看到这里,也应该想到了,我们要用并查集来解决这个子任务。根据前面的结论,只要每一个连通块的权值总和等于b中对应联通的联通块的权值总和,即能成功变形为b序列。即上图中的a(i + j + k) == b(i) + b(j) + b(k)时,能完成变形。
因此,我们可以用并查集存储每一个连通块的父节点,用一个va数组来存储a序列和b序列中每一个连通块的权值总和(下标1到n存储a的权值和,n + 1到2n存储b的权值和)。
那么我们就可以写出代码了:

void merge(int x, int y) { //合并操作,传入的x和y分别是操作二中的u和v
	int fx = find(x), fy = find(y); //找到两个点的父节点,直接对父节点进行操作
	va[fy] += va[fx], va[fx] = 0; //更新连通块的权值和
	p[fx] = fy; //合并
}

for(int i = 1 ; i <= 2 * n ; i ++ ) p[i] = i; //并查集初始化
for(int i = 1 ; i <= n ; i ++ ) 
	va[i] = a[i], va[i + n] = b[i]; //开始时每个序列中有n个连通块,每个连通块都只有一个数,因此需要初始化va数组

for (int i = 1 ; i <= m ; i ++ ) { //构造并查集与
	int x = u[i], y = v[i]; //一次操作二中需要合并的两个点
		if (find(x) == find(y)) continue; //如果已经合并过了,就不管了
		merge(x, y); //对a序列进行更新
		merge(x + n, y + n); //对b序列进行更新
	}

bool flag = true;
for (int i = 1 ; i <= n ; i ++ ) //判断合法性
	if (find(i) == i) //由于连通块的权值和存储在父节点上,所以只需找到父节点
		if (va[i] != va[i + n]) { //如果这个连通块在a和b两个序列中权值总和不同,则无法成功变形
			puts("NO");
			flag = false;
			break;
		}
if (flag) puts("YES");

全部代码:

#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 1e5 + 10;
int n, m;
int p[N * 2], va[N * 2];
int a[N], b[N], u[N], v[N], t[N];

int find(int x) {
	if (p[x] != x)
		p[x] = find(p[x]);
	return p[x];
}

void merge(int x, int y) { //合并操作
	int fx = find(x), fy = find(y);
	va[fy] += va[fx], va[fx] = 0;
	p[fx] = fy;
}

int main() {
	int T;
	scanf("%d", &T);
	while (T -- ) {
		scanf("%d%d", &n, &m);
		for (int i = 1 ; i <= n * 2 ; i ++ )
			p[i] = i;
		for (int i = 1 ; i <= n ; i ++ )
			scanf("%d", a + i), va[i] = a[i];
		for (int i = 1 ; i <= n ; i ++ )
			scanf("%d", b + i), va[n + i] = b[i];
		for (int i = 1 ; i <= m ; i ++ ) 
			scanf("%d%d%d", t + i, u + i, v + i);
		if(n == 2){
    	    if(m == 1){
    		    if(t[1] == 1){
    		        if(a[1] - b[1] == a[2] - b[2]) puts("YES");
    		        else puts("NO");
    		    }
    		    else{
    		        if(a[1] + a[2] == b[1] + b[2]) puts("YES");
    		        else puts("NO");
    		    }
    	    }
    	    else{
    	        if((a[1] - a[2] + b[2] - b[1]) % 2 == 0) puts("YES");
    	        else puts("NO");
    	    }
    	    continue;
		}
		for (int i = 1 ; i <= m ; i ++ ) {
			int x = u[i], y = v[i]; //需要合并的两个点
			if (find(x) == find(y))
				continue; //如果已经合并过了,就跳过
			merge(x, y); //对原数组进行合并
			merge(x + n, y + n); //对目标数组进行合并
		}
		bool flag = true;
		for (int i = 1 ; i <= n ; i ++ )
			if (find(i) == i)
				if (va[i] != va[i + n]) {
					puts("NO");
					flag = false;
					break;
				}
		if (flag)
			puts("YES");
	}
	return 0;
}

看到这里,你可能想问:前四个子任务不就16个点吗?你不是说能过19个点吗?
其实最后那三个没有任何特殊限制的测试点我也不知道是怎么过的,可能是因为数据比较水罢(

所以如果有大神能AC这道题,还请多多指教!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值