Codeforces Round #829 (Div. 2) C2. Make Nonzero Sum (hard version) 解题报告

原题链接:

Problem - C2 - Codeforces

题目描述:

This is the hard version of the problem. The difference is that in this version the array contains zeros. You can make hacks only if both versions of the problem are solved.

You are given an array [a1,a2,…an][a1,a2,…an] consisting of integers −1−1, 00 and 11. You have to build a partition of this array into the set of segments [l1,r1],[l2,r2],…,[lk,rk][l1,r1],[l2,r2],…,[lk,rk] with the following property:

  • Denote the alternating sum of all elements of the ii-th segment as sisi: sisi = ali−ali+1+ali+2−ali+3+…±ariali−ali+1+ali+2−ali+3+…±ari. For example, the alternating sum of elements of segment [2,4][2,4] in array [1,0,−1,1,1][1,0,−1,1,1] equals to 0−(−1)+1=20−(−1)+1=2.
  • The sum of sisi over all segments of partition should be equal to zero.

Note that each sisi does not have to be equal to zero, this property is about sum of sisi over all segments of partition.

The set of segments [l1,r1],[l2,r2],…,[lk,rk][l1,r1],[l2,r2],…,[lk,rk] is called a partition of the array aa of length nn if 1=l1≤r1,l2≤r2,…,lk≤rk=n1=l1≤r1,l2≤r2,…,lk≤rk=n and ri+1=li+1ri+1=li+1 for all i=1,2,…k−1i=1,2,…k−1. In other words, each element of the array must belong to exactly one segment.

You have to build a partition of the given array with properties described above or determine that such partition does not exist.

Note that it is not required to minimize the number of segments in the partition.

Input

Each test contains multiple test cases. The first line contains the number of test cases tt (1≤t≤100001≤t≤10000). Description of the test cases follows.

The first line of each test case contains an integer nn (1≤n≤2000001≤n≤200000) — the length of array aa.

The second line of each test case contains nn integers a1,a2,…,ana1,a2,…,an (aiai is −1−1, 00, or 11) — the elements of the given array.

It's guaranteed that the sum of nn over all test cases does not exceed 200000200000.

Output

For each test case print an integer kk — the number of segments in the partition. If required partition does not exist, print −1−1.

If partition exists, in the ii-th of the following kk lines print two integers lili and riri — description of the ii-th segment. The following conditions should be satisfied:

  • li≤rili≤ri for each ii from 11 to kk.
  • li+1=ri+1li+1=ri+1 for each ii from 11 to (k−1)(k−1).
  • l1=1,rk=nl1=1,rk=n.

If there are multiple correct partitions of the array, print any of them.

题目大意:

C题的hard版本,给定一个长度为n的数组,数组元素包含1和-1以及0,我们可以把整个数组段分割为若干个连续子段,每个子段[l, r]的value为a[l]-a[l+1]+a[l+2]-a[l+3]……,以此类推,每个子段上的奇数位做加法,偶数位做减法,请问如何分割子段,可以使得各个子段的value之和为0,不比使得子段数量最小,如果有多种答案,可以输出任意一种,如果没有满足要求的答案,则输出-1。

解题思路:

相比easy版本,唯一的区别就是在不仅有-1和1,还有0。我们可以先预处理,先假定整个数组划分为n个长度为1的小区间,那么整个value值就为所有元素的和。我们根据value的正负来分别改动区间的合并情况,如果value大于零,那么我们每次遇到1并且他左边那个元素并没有被前边的区间合并,那么我们可以把这两个元素合并为一个区间,这样总的value就比原来减了2,因为原本这个位置是加1,现在变成了减1。value小于零的情况则相反。我们会发现每次改变value都是+2或者-2,所以如果一开始预处理的value值为奇数,则不可能有解。具体细节详见代码。

代码(CPP):

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long
#define PII pair<long long, long long>
typedef unsigned long long ull;
const int maxn = 2e5 + 10;
const int INF = 0x3fffffff;
int n, a[maxn];
vector<PII> seg;  // 存放区间端点
int Merge[maxn];  // 记录区间合并情况, Merge[6]=2代表[2,6]被合并

bool cmp(PII a, PII b)
{
    return a.first < b.first;
}

signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    cout << fixed;
    cout.precision(18);

    int t;
    cin >> t;
    while (t--)
    {
        seg.clear();
        cin >> n;
        for (int i = 1; i <= n; i++)
            Merge[i] = i;
        int value = 0;
        // 预处理,先把每个数组元素看作一个单独的长度为1的小区间
        for (int i = 1; i <= n; i++)
        {
            cin >> a[i];
            value += a[i];
        }
        // 如果预处理的总贡献为奇数则无解
        if(value & 1)
        {
            cout << "-1\n";
            continue;
        }
        // 根据总贡献的正负来调整区间合并,将总贡献变为0
        for (int i = 2; i <= n; i++)
        {
            if(value == 0)
                break;
            if(value > 0)
            {
                if (a[i] == 1 && Merge[i - 1] == i - 1)
                {
                    Merge[i] = i - 1;
                    value -= 2;
                }
            }
            else
            {
                if (a[i] == -1 && Merge[i - 1] == i - 1)
                {
                    Merge[i] = i - 1;
                    value += 2;
                }
            }
        }
        // 根据Merge标记数组,还原出所有的区间合并情况
        for (int i = n; i >= 1;)
        {
            seg.push_back({Merge[i], i});
            if(Merge[i] == i)
                i--;
            else
                i = Merge[i] - 1;
        }
        // 将区间按照左端点排序并且输出
        sort(seg.begin(), seg.end(), cmp);
        cout << seg.size() << endl;
        for (int i = 0; i < seg.size(); i++)
        {
            cout << seg[i].first << " " << seg[i].second << endl;
        }
    }
    return 0;
}

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值