D1,0,-1构造
题面
这是该问题的困难版本。不同的是,在这个版本中,数组包含零。只有当两个版本的问题都解决了,你才能进行黑客攻击。
给你一个数组[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。
.
在第三个测试案例中,可以证明所需的分区并不存在。
思路
题目最终目的
要让所有的段和等于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)问题是在代码中还是在想法中?
走完上面的流程说明可能是想法有问题,检查想法是否能解决问题,是否有没有考虑的点。