Codeforces Round 939 (Div. 2) D. Nene and the Mex Operator 题解 二进制枚举+递归

Nene and the Mex Operator

题目描述

Nene给了你一个长度为 n n n 的整数数组 a 1 , a 2 , … , a n a_1, a_2, \ldots, a_n a1,a2,,an

你可以执行以下操作不超过 5 ⋅ 1 0 5 5\cdot 10^5 5105 次(可能为零):

  • 选择两个整数 l l l r r r ,使得 1 ≤ l ≤ r ≤ n 1 \le l \le r \le n 1lrn ,计算 x x x MEX ⁡ ( { a l , a l + 1 , … , a r } ) \operatorname{MEX}(\{a_l, a_{l+1}, \ldots, a_r\}) MEX({al,al+1,,ar}) ,同时设置 a l = x , a l + 1 = x , … , a r = x a_l=x, a_{l+1}=x, \ldots, a_r=x al=x,al+1=x,,ar=x

这里,整数集合 { c 1 , c 2 , … , c k } \{c_1, c_2, \ldots, c_k\} {c1,c2,,ck} 中的 MEX ⁡ \operatorname{MEX} MEX 被定义为在集合 c c c 中不出现的最小非负整数 m m m

你的目标是最大化数组 a a a 中元素的和。找出最大和,并构建一个操作序列来实现这个和。需要注意的是,你不需要最小化这个序列中的运算次数,你只需在解决方案中使用不超过 5 ⋅ 1 0 5 5\cdot 10^5 5105 的运算。

输入描述

第一行包含一个整数 n n n 1 ≤ n ≤ 18 1 \le n \le 18 1n18 )- 数组 a a a 的长度。

第二行包含 n n n 个整数 a 1 , a 2 , … , a n a_1,a_2,\ldots,a_n a1,a2,,an 0 ≤ a i ≤ 1 0 7 0\leq a_i \leq 10^7 0ai107 )。

输出描述

在第一行中,输出两个整数 s s s m m m ( 0 ≤ m ≤ 5 ⋅ 1 0 5 0\le m\le 5\cdot 10^5 0m5105 ) - 数组 a a a 元素的最大和以及解决方案中的操作数。

在下面 m m m 行的 i i i -th 中,输出两个整数 l l l r r r ( 1 ≤ l ≤ r ≤ n 1 \le l \le r \le n 1lrn ),代表 i i i -th 运算的参数。

可以证明,数组 a a a 元素的最大和总是可以通过不超过 5 ⋅ 1 0 5 5 \cdot 10^5 5105 次的运算得到。

样例输入1

2
0 1

样例输出1

4 1
1 2

样例输入2

3
1 3 9

样例输出2

13 0

样例输入3

4
1 100 2 1

样例输出3

105 2
3 3
3 4

样例输入4

1
0

样例输出4

1 1
1 1

原题

CF Round 939——传送门

思路

对于任意一段区间 [ l , r ] [l,r] [l,r],我们总能进行一系列操作,使该区间的和为 ( r − l + 1 ) ∗ ( r − l + 1 ) (r-l+1)*(r-l+1) (rl+1)(rl+1)。所以,由于数组的长度最大为 18 18 18,我们可以通过二进制枚举所有对区间操作的方案数,找到使得和最大的其中一种方案。找到了使和最大的方案后,需要输出对于各个区间的操作。那么我们考虑任意一段区间 [ l , r ] [l,r] [l,r],先将其中所有元素置 0 0 0,因为需要将该区间转化为 { r − l + 1 , r − l + 1 , … , r − l + 1 } \{r-l+1, r-l+1, \ldots, r-l+1\} {rl+1,rl+1,,rl+1},所以先递归得到 { r − l , r − l , … , r − l , r − l , 0 } \{r-l, r-l, \ldots, r-l , r-l,0\} {rl,rl,,rl,rl,0},然后将 [ l , r − 2 ] [l,r-2] [l,r2]的部分置 0 0 0,进行不断递归,直至区间转化为 { 1 , 2 , 3 , … , r − l , 0 } \{1, 2, 3, \ldots, r-l,0\} {1,2,3,,rl,0},然后再对整个区间进行一次操作,即可得到 { r − l + 1 , r − l + 1 , … , r − l + 1 } \{r-l+1, r-l+1, \ldots, r-l+1\} {rl+1,rl+1,,rl+1}

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

void set_zero(int l, int r, vector<int> &a, vector<pair<int, int>> &ans)
{
    for (int i = l; i <= r; i++)
    {
        if (a[i] != 0)
        {
            ans.push_back({i, i});
        }
    }
}

void set_max(int l, int r, vector<pair<int, int>> &ans)
{
    if (l == r)
    {
        ans.push_back({l, r});
        return;
    }
    for (int k = r - 1; k >= l; k--)
    {
        set_max(l, k, ans);
        if (k != l)
            ans.push_back({l, k - 1});
    }
    ans.push_back({l, r});
}

int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);

    // 对于任意一段区间[l,r],我们总能进行一系列操作,使该区间的和为 (r-l+1)*(r-l+1)
    // 所以,由于数组的长度最大为18,我们可以通过二进制枚举所有对区间操作的方案数,找到使得和最大的其中一种方案
    int n;
    cin >> n;
    vector<int> a(n);
    for (auto &x : a)
        cin >> x;
    vector<pair<int, int>> op;         // 记录和最大时需要操作的区间
    int max_sum = 0;                   // 记录最大和
    for (int i = 0; i < (1 << n); i++) // 二进制枚举所有区间操作方案,连续的一段1是需要进行区间操作的
    {
        ll sum = 0;                 // 当前方案的总和
        vector<pair<int, int>> tmp; // 当前方案需要操作的区间
        for (int j = 0; j < n;)
        {
            if ((i >> j) & 1)
            {
                int l = j;
                int r = j;
                r++;
                while (r < n && ((i >> r) & 1)) // 找到连续的一段1
                {
                    r++;
                }
                tmp.push_back({l, r - 1});
                sum += (r - l) * (r - l); // 找到的区间为[l,r)
                j = r;
            }
            else
            {
                sum += a[j];
                j++;
            }
        }
        if (sum > max_sum)
        {
            op = tmp;
            max_sum = sum;
        }
    }
    cout << max_sum << ' '; // 输出最大和

    // 那么下面的任务就是输出实现这种方案的所需操作
    vector<pair<int, int>> ans; // 记录所需操作
    // 先将区间元素全部置0
    for (int i = 0; i < op.size(); i++)
    {
        set_zero(op[i].first, op[i].second, a, ans);
    }
    // 再将区间元素全部化为r-l+1
    for (int i = 0; i < op.size(); i++)
    {
        set_max(op[i].first, op[i].second, ans);
    }
    // 输出操作
    cout << ans.size() << '\n';
    for (int i = 0; i < ans.size(); i++)
    {
        cout << ans[i].first + 1 << ' ' << ans[i].second + 1 << '\n';
    }

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值