(1,0,-1)构造cf1754C2 Make Nonzero Sum (hard version)

D1,0,-1构造

题面

Problem - 1754C2 - Codeforces

这是该问题的困难版本。不同的是,在这个版本中,数组包含零。只有当两个版本的问题都解决了,你才能进行黑客攻击。

给你一个数组[a1,a2,…an] 。
由整数-1组成
, 0
和1
. 你必须将这个数组分割成[l1,r1],[l2,r2],…,[lk,rk]这一组段。
具有以下性质:

将第i个段的所有元素的交替之和表示为
-的所有元素的交替总和表示为si
: si
= ali - ali+1 +ali+2 -ali+3 +…±ari
. 例如,段[2,4]的元素的交替和
在数组[1,0,-1,1,1]中的元素交替相加。
等于0-(-1)+1=2
.
在分区的所有段上,si的总和
的总和应该等于零。
请注意,每个si
不一定等于零,这个属性是关于分区所有段的si之和
的总和。

段落[l1,r1],[l2,r2],…,[lk,rk]的集合称为数组的分区。
被称为数组a的一个分区
长度为n
如果1=l[1]≤r[1],l[2]≤r[2],…,l[k]≤r[k]=n
并且ri+1=l[i+1]
对于所有i=1,2,…k-1
. 换句话说,数组的每个元素必须正好属于一个段。

你必须建立一个具有上述属性的给定数组的分区,或者确定这种分区不存在。

请注意,并不要求分区中的段数最小。

输入
每个测试包含多个测试用例。第一行包含测试用例的数量t
(1≤t≤10000
). 测试用例的描述如下。

每个测试用例的第一行包含一个整数n
(1≤n≤200000
)–数组a的长度
.

每个测试用例的第二行包含n
整数a1,a2,…,an
(ai
是 -1
, 0
,或1
) - 给定数组的元素。

保证所有测试案例的n之和
的总和不超过200000
.

输出
对于每个测试案例打印一个整数k

  • 分区中的段数。如果需要的分区不存在,打印-1
    .

如果分区存在,在第i
-中的第i
行中打印两个整数li
和ri

  • 对第i段的描述
    -段的描述。应满足以下条件:

li≤ri
对于每个i
从1
到k
.
li+1=ri+1
对于每个i
从1
到(k-1)
.
l1=1, rk=n
.
如果有多个正确的数组分区,打印其中任何一个。

例子
输入复制
5
4
0 0 0 0
7
-1 1 0 1 0 1 0
5
0 -1 1 0 1
3
1 0 1
1
1
输出拷贝
4
1 1
2 2
3 3
4 4
4
1 1
2 2
3 5
6 7
-1
2
1 1
2 3
-1
注意
在第一个测试案例中,我们可以建立一个4段的分区
分区 - 每个分区只包含一个等于0的数组元素
. 所以总和将等于0+0+0+0=0
.

在第二个测试案例中,我们可以建立一个4段的分区。
段。第一段的交替之和将等于-1
,第二段的交替之和将等于1
,第三段 - 0-1+0=-1
,第四段 - 1-0=1
. 总和将等于-1+1-1+1=0。
.

在第三个测试案例中,可以证明所需的分区并不存在。

Translated with DeepL

思路

题目最终目的
要让所有的段和等于0.

切入点

0,-1,1三种元素,
段长全部等于1时,符号全是+
存在1 和-1互相抵消,还有可能有多余的1或-1,

如何处理?

题目提供了一种方式,段长等于2时,后一个元素的符号变成-号。
所以通过这个符号,将多余的1或-1的一半变成符号,然后和没有变化的部分抵消。

为什么有一半可以转变符号?

边界条件下,没有其他元素可以给1作为段的第一个元素,则要另一个1来充当这个角色,1的总数是需要变换的一半,只要拿出一半作为段的第一个元素,另一半作为段的后一半元素,恰好足够。

此时就知道,当多余1或-1的数量为奇数时,无法互相抵消干净,无解。
偶数情况才有解。接着就是要求多余的1或-1数量了,直接将所有元素相加得到的和就是了。

所以步骤是:
相加,判断奇偶,输出。

直觉上比较棘手的操作:

下标组别的输出

拆分棘手问题:

1)首先是解决段数问题
长度全为1时,段数是n,也就是数组长度,
每有一个需要变换的1或-1,就要多一个长度为2的段,少两个长度为1的段,总体上是段数少一个,而需要变换的1或-1数量等于数组元素总和,所以最终段数就是数组长度 - 数组元素总和。
2)再就是下标问题:
要顺序输出,所以倒序遍历存答案或是顺序遍历直接输出,
显然顺序遍历比较容易实现。
主要是要判断当前遍历的元素的后一个元素是否为1或-1,
因为是判断后一个元素,所以遍历的区间是1-n-1,
3)再就是判断的问题,要处理的元素是1还是-1?
如果总和大于0,就是1,小于0就是-1.
写一个判断正负的语句,然后分支进入1或-1即可。
然后用一个bool将判断结果保存并带出来。
4)最后是输出的问题:
成功且还有操作次数则i i+1\n,然后应该直接i++,然后这轮循环结束i++,
否则,直接输出i i\n,

思路缺陷(已修改):
没有考虑要处理多少次的问题与解决措施。
还有数组总和到操作次数的考量欠妥,没有考虑要/2才能转换转换

代码

题解代码。。57行,实现太复杂。
#include<iostream>
#include<cmath>
#include<vector>
using namespace std;
 
typedef pair<int, int> PII;
const int N = 2e5 + 5;
int a[N], n, num;
 
void MakeNoneZeroSum(int k) {
 
	vector<PII> res;  //记录结果
	int cnt_n = 0;   //记录已成调和组数
 
	for(int i = 1; i <= n; i ++) {
		if(cnt_n < abs(num/2) && a[i+1] == k) {  //当组数未达到且满足要求
			++ cnt_n;
			res.push_back({i, i+1});
			i ++;
		} else {
			res.push_back({i, i});
		}
	}
 
	cout << res.size() << endl;
	for(auto tmp: res)
		cout << tmp.first << ' ' << tmp.second << endl;
}
 
int main() {
	int t;
	cin >> t;
	while(t --) {
		cin >> n;
 
		num = 0;
		for(int i = 1; i <= n; ++ i) {
			cin >> a[i];
			num += a[i];
		}
 
		if(abs(num) % 2) {
			cout << -1 << endl;
			continue;
		}
 
		if(num == 0) {
			cout << n << endl;
			for(int i = 1; i <= n; ++ i)
				cout << i << ' ' << i << endl;
		} 
		else if(num > 0) MakeNoneZeroSum(1);
		else  MakeNoneZeroSum(-1);
	}
 
	return 0;
}
bug代码,把注释的bug修复就能ac:
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 2e5 + 10;
int ar[N];
int main(){
	int T;
	cin >> T;
	while(T--){
		int n;
		scanf("%d",&n);
		for(int i = 1;i <= n;i++){
			scanf("%d",&ar[i]);
		}
		int sum = 0;
		for(int i = 1;i <= n;i++) sum+= ar[i];
		if(sum % 2)cout << -1 << endl;
		else{
			sum /= 2;
			cout << n - sum << endl;
bug1,sum有正有负,减法操作一定要abs。
			for(int i = 1;i <= n;i++){
				bool flag = 0;
				if(sum>0 && ar[i+1] == 1) flag = true;
				else if(sum < 0 && ar[i+1] == -1) flag = true;
				if(flag && sum){
					printf("%d %d\n",i,i+1);
					i++;
					if(sum > 0) sum -=2;
bug2,sum/=2后,意义已经变为了要处理的次数,所以每次变换应该是+=1-=1
					else sum += 2;
				}
				else {
					printf("%d %d\n",i,i);
				}
			}
			
		}
	}
	return 0;
}

抽象出的套路(参考um_nik):

构造

通过题目条件的推理,构造出一种简单操作,可以通过这种简单操作使得元素变得符合题意

前提:元素种类,-1,0,1
应对

如-1和1可以通过数组求和来判断出数量关系
可以通过操作获得元素情况,找到特殊情况元素,用特殊情况元素来操作其他元素或接受操作。
解题相关

读题

该问题要求我做什么?
我可以把它重新表述为某个标准问题吗?是对某个标准问题的发挥吗?****

解题前:

复杂性是什么?
我对问题的理解是否正确?
我需要哪些功能?
哪些地方比较棘手?为了正确实现它们,我需要记住什么?
哪个地方的实现是最重的?我可以做得更简单吗?

这次尝试了找棘手的地方来细写方法与注意确实有用。


对于如何迭代:
检查自己哪些地方花的时间超出了预期。

1.debug:花了20min,太长了

要做的更快,需要按流程走一遍:

debug流程

1)你是否记得做了所有的事情来处理你之前想到的那些棘手的地方?

是否写了代码来处理之前想到要处理的地方
一一指出处理点与对应代码

2)解决方案是否做了它应该做的事?

代码是否存在漏洞使得实现的功能与想法不同
像分析题解一样分析代码功能,看是否有错漏

3)我对问题的理解是否正确?

是否理解错了题意,
再看一遍题目

4)问题是在代码中还是在想法中?

走完上面的流程说明可能是想法有问题,检查想法是否能解决问题,是否有没有考虑的点。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值