CF1567A-E

B. MEXor Mixup

有一未知数组的mex值为a,异或值为b,求数组的最小长度。

由于mex值为a,因此至少需要 [0,a-1] 这a个元素。令前a个元素的异或值为x,

  • 若x=b,则只需要前a个元素
  • 若x!=b,显然存在一个数y满足x^y=b,但是需要判断y是否是a,即是否会破坏mex条件。

代码

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

const int N = 3e5 + 5;

int pre[N];
void solve()
{
    int a, b;
    scanf("%d%d", &a, &b);
    int res = 0;
    res = pre[a - 1];
    if (res == b)
        printf("%d\n", a);
    else if ((res ^ b) == a)
        printf("%d\n", a + 2);
    else
        printf("%d\n", a + 1);
}

int main()
{
    for (int i = 1; i < N; i++)
        pre[i] = pre[i - 1] ^ i;
    int t;
    scanf("%d", &t);
    while (t--)
        solve();
    return 0;
}

C. Carrying Conundrum

赛时被C卡到自闭。

Solution I

奇数位进位到奇数位,偶数位进位到偶数位,因此可以将奇偶位分开来考虑,然后用乘法原理计算贡献。

设一个数为ABCDEF,拆成ACE和BDF两个数,单独考虑ACE,求a+b=ACE的方案数,显然方案数为ACE+1。同理 BDF的方案数为BDF+1,答案就是 ( A C E + 1 ) × ( B D F + 1 ) (ACE+1)\times (BDF+1) (ACE+1)×(BDF+1)再减去a为0,b为0 两种情况

代码

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

const int N = 1e5 + 5;

void solve()
{
    string a;
    cin >> a;
    int s[2], p = 0;
    s[0] = s[1] = 0;
    for (auto it : a)
    {
        s[p] = s[p] * 10 + it - '0';
        p ^= 1;
    }
    cout << (s[0] + 1) * (s[1] + 1) - 2 << endl;
}
int main()
{
    int t;
    cin >> t;
    while (t--)
        solve();

    return 0;
}

Solution II

可以考虑枚举进位的情况。

代码

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

int num[22];
void solve()
{

    string n;
    cin >> n;
    vector<int> vec(12);
    int len = n.size();
    for (int i = 0; i < len; i++)
        vec[10 - (len - i - 1)] = n[i] - '0';
    int ans = 0;
    for (int i = 0; i < (1 << 10); i++)
    {
        if (i % 4)
            continue;
        vector<int> temp = vec;
        bool flag = 1;
        int res = 1;
        for (int j = 0; j < 10; j++)
        {
            int p = 10 - j;
            if ((1 << j) & i)
                temp[p]--;
            if ((1 << (j + 2)) & i)
                temp[p] += 10;
            res *= num[temp[p]];
        }
        if (flag)
            ans += res;
    }
    printf("%d\n", ans - 2);
}

int main()
{
    for (int a = 0; a <= 9; a++)
        for (int b = 0; b <= 9; b++)
            for (int c = 0; c <= 18; c++)
                if (a + b == c)
                    num[c]++;
    int t;
    scanf("%d", &t);
    while (t--)
        solve();
    return 0;
}

D. Carrying Conundrum

对比一下十进制和十一进制

100 10 1
121 11 1

结论:将数放在尽可能的高位上是最优的。

但是又要满足所有数都不为0,因此贪心的将低位的数进行拆分,这样损失的贡献最少。

代码

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

const int N = 1e2 + 5;

int a[N];
int p10[N];

void solve()
{
    int s, n;
    scanf("%d %d", &s, &n);
    for (int i = 1; i <= n; i++)
        a[i] = 0;
    int num[100];
    int p = 0;
    while (s)
    {
        num[++p] = s % 10;
        s /= 10;
    }
    while (1)
    {
        int sum = 0;
        for (int i = 1; i <= p; i++)
            sum += num[i];
        if (sum < n)
        {
            for (int i = 2; i <= p; i++)
            {
                if (num[i])
                {
                    num[i]--;
                    num[i - 1] += 10;
                    break;
                }
            }
        }
        else
            break;
    }
    int pos = 1;

    for (int j = 1; j <= p; j++)
    {
        while (num[j]--)
        {
            a[pos++] += p10[j];
            if (pos > n)
                pos = 1;
        }
    }
    for (int i = 1; i <= n; i++)
        printf("%d ", a[i]);
    printf("\n");
}

void init()
{
    p10[1] = 1;
    for (int i = 2; i <= 10; i++)
        p10[i] = p10[i - 1] * 10;
}
int main()
{
    init();
    int t;
    scanf("%d", &t);
    while (t--)
        solve();

    return 0;
}

E. Carrying Conundrum

比较显然的线段树维护。

考虑合并两个区间时,怎么维护询问要求的 区间的贡献。

对于最底层、长度为1的区间(单个元素),显然贡献是1,合并两个长度为1的区间,贡献即是两个子区间的贡献和,再加上区间合并带来的新增贡献。只需维护好新增的贡献即可。

考虑维护新增贡献。

假设左区间为1 2 3 1 2 3 。右区间为4 5 1 2。
记左区间的最长的以右端点结束的不下降区间长度为 M X R MX_R MXR​
记右区间的最长的以左端点开始的不下降区间长度为 M X L MX_L MXL​
当左区间的右端点值小于右区间的左端点值时,显然新增贡献为 M X R × M X L MX_R\times MX_L MXR​×MXL​。

那么对于每个区间,还需要维护好MX_L、MX_R。

考虑维护MX_L。(MX_R同理)

还是以上面两个区间为例,现有:

LSON.MX_L=3
LSON.MX_R=3
RSON.MX_R=2
RSON.MX_R=2

要求

FATHER.MX_L
FATHER.MX_R

这个条件总是成立:

FATHER.MX_L=LSON.MX_L
FATHER.MX_R=RSON.MX_R

那么FATHER.MX_L (/FATHER.MX_R)能否进行拓展呢?

当左区间的右端点值小于右区间的左端点值、且LSON.MX_L为LSON的整个区间长度时,才可以进行拓展。R同理。

另外,询问时需要注意的问题:假设询问区间为[5,6],可能在线段树上询问的是LSON[1,5],RSON[6,10],这时候LSON.MX_R和RSON.MX_L就需要和询问区间取一个min

代码:

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

const int N = 2e5 + 5;

int a[N];

struct node
{
    int k, l, r, mx_L, mx_R;
    long long sum;
    //mx_L 从左端点开始 最长的不下降子区间长度
    //mx_R 以右端点结束 最长的不下降子区间长度
} tr[N << 2];

void push_up(int k)
{
    long long res = 0;
    tr[k].mx_L = tr[k << 1].mx_L;
    tr[k].mx_R = tr[k << 1 | 1].mx_R;
    if (a[tr[k << 1].r] <= a[tr[k << 1 | 1].l]) //考虑连接两个区间
    {
        res = 1ll * (tr[k << 1].mx_R) * (tr[k << 1 | 1].mx_L);
        if (tr[k << 1].mx_L == tr[k << 1].r - tr[k << 1].l + 1) //如果整个左儿子区间都是不下降的,那么可以合并
            tr[k].mx_L = tr[k << 1 | 1].mx_L + (tr[k << 1].r - tr[k << 1].l + 1);

        if (tr[k << 1 | 1].mx_R == tr[k << 1 | 1].r - tr[k << 1 | 1].l + 1) //如果整个右儿子区间都是不下降的,那么可以合并
            tr[k].mx_R = tr[k << 1].mx_R + (tr[k << 1 | 1].r - tr[k << 1 | 1].l + 1);
    }
    tr[k].sum = tr[k << 1 | 1].sum + tr[k << 1].sum + res;
}

void build(int k, int l, int r)
{
    tr[k].l = l, tr[k].r = r;
    if (l == r)
    {
        tr[k].sum = tr[k].mx_L = tr[k].mx_R = 1;
        return;
    }
    int mid = l + r >> 1;
    build(k << 1, l, mid);
    build(k << 1 | 1, mid + 1, r);
    push_up(k);
}
void change(int k, int pos, int val)
{
    if (tr[k].l == tr[k].r)
    {
        tr[k].sum = tr[k].mx_L = tr[k].mx_R = 1;
        return;
    }
    int mid = tr[k].l + tr[k].r >> 1;
    if (pos <= mid)
        change(k << 1, pos, val);
    else
        change(k << 1 | 1, pos, val);
    push_up(k);
}
long long query(int k, int l, int r)
{
    if (tr[k].l == l && tr[k].r == r)
        return tr[k].sum;
    int mid = tr[k].l + tr[k].r >> 1;
    long long ans = 0;
    if (r <= mid)
        ans += query(k << 1, l, r);
    else if (l > mid)
        ans += query(k << 1 | 1, l, r);
    else
    {
        ans += query(k << 1, l, mid);
        ans += query(k << 1 | 1, mid + 1, r);

        if (a[tr[k << 1].r] <= a[tr[k << 1 | 1].l]) //中间的贡献,注意要和区间大小取min。
            ans += 1ll * min(mid - l + 1, tr[k << 1].mx_R) *
                   min(r - (mid + 1) + 1, tr[k << 1 | 1].mx_L);
    }
    return ans;
}

void solve()
{
    int n, q;
    scanf("%d %d", &n, &q);
    for (int i = 1; i <= n; i++)
        scanf("%d", &a[i]);
    build(1, 1, n);
    while (q--)
    {
        int opt, l, r;
        scanf("%d %d %d", &opt, &l, &r);
        if (opt == 1)
        {
            a[l] = r;
            change(1, l, r);
        }
        else
            printf("%lld\n", query(1, l, r));
    }
}

int main()
{
    solve();
    return 0;
}
上一篇:Educational Codeforces Round 113 (Rated for Div. 2) 补题 A B C


下一篇:CF 1569C Jury Meeting