CF Round 698(Div2) 解题补题报告

官方题解

A题 Nezzar and Colorful Balls (贪心/DP)

\(T(1\leq T \leq 100)\) 组数据。

给定一个长度为 \(n(n \leq 100)\) 的单调不降数列,第 \(i\) 项为 \(a_i(1\leq a_i \leq n)\)。现在尝试将其划分为 \(k\) 个严格单调递增的数列,请求出 \(k\) 的最小值。

高中写过 导弹拦截 的人,看到题面后基本上都会会心一笑:对于任意一个序列,将其划分成多个严格单调递增的子数列的最小数目,相当于该数列的最长单调不增子数列的长度。

也就是说,对于每组数据,我们只要 \(O(n^2)\) 的求出最长单调不增子数列的长度就行了,复杂度完全可以接受。

给出代码:

#include<bits/stdc++.h>
using namespace std;
const int N = 110;
int n, a[N], dp[N];
void solve()
{
    scanf("%d", &n);
    for (int i = 1; i <= n; ++i)
        scanf("%d", &a[i]);
    memset(dp, 0, sizeof(dp));
    int ans = -1;
    for (int i = 1; i <= n; ++i) {
        dp[i] = 1;
        for (int j = 1; j < i; ++j) {
            if (a[j] >= a[i])
                dp[i] = max(dp[j] + 1, dp[i]);
        }
        ans = max(ans, dp[i]);
    }
    printf("%d\n", ans);
}

int main()
{
    int T;
    scanf("%d", &T);
    while (T--) {
        solve();
    }
    return 0;
}

不过呢,题目中似乎有一个性质没用到:原数列本身就是单调不降的。

这样的话,都不需要知道上面那个定理,简单推理一下就可以得到答案:原数列中最长的常数列的长度,也就是众数出现的个数(逃

代码就不给了(因为懒

B题 Nezzar and Lucky Number (数学(+DP))

\(T(1\leq T \leq 9)\) 组数据。

给定正整数 \(d(1\leq d \leq 9)\),如果一个数字的十进制表示中含有 \(d\),那么这个数字就是完美的。

现在给定 \(n(n \leq 10^4)\) 个正整数,第 \(i\) 个数是 \(a_i(a_i \leq 10^9)\),依次判断他们是不是能够表示成多个完美的数的和。

我们给出一个不太显然的结论:如果一个数 \(x\)大于等于 \(10d\),那她就可以表示成多个完美的数字之和。

显然,\(x\) 可以表示成 \(kd+m\) 的形式(\(k \geq 10,0 \leq m \leq 9\)),也就是 \((10d + m) + d + ... + d\) 的形式,显然是可以表示成多个完美数的和的。

对于 \(x<10d\) 的情况呢?

我当时想到了一个显然的结论:如果 \(x\) 是完美的,那么 \(x+kd\) 也是完美的 (\(k \geq 0\))。

那么,对于一个数字 \(x\),我们似乎可以对其不断减去 \(d\),看看有没有减到某一步的时候出现一个完美数。反正我考场上就这么写的,直接过了。

#include<bits/stdc++.h>
using namespace std;
int n, d;
bool exists(int x) {
    while (x) {
        if (d == x % 10) return true;
        x /= 10;
    }
    return false;
}
bool can(int x) {
    if (exists(x) || x % d == 0) return true;
    while (x >= d) {
        x -= d;
        if (exists(x)) return true;
    }
    return false;
}
void solve()
{
    scanf("%d%d", &n, &d);
    for (int i = 1, tmp; i <= n; ++i) {
        scanf("%d", &tmp);
        puts(can(tmp) ? "YES" : "NO");
    }
}

int main()
{
    int T;
    scanf("%d", &T);
    while (T--) {
        solve();
    }
    return 0;
}

现在尝试给出严格证明:

显然 \(x\) 是一个二位数或者一位数。(一位数看一下能不能被 \(d\) 整除就行了,我们重点讨论二位数)

所以说,构成 \(x\) 的那些完美数也都是二位数或者一位数,而且如果是二位数,显然他们的十位不可能是 \(d\) (如果这样,那么 \(x \geq 10d\),和已知条件矛盾)。

也就是说,构成 \(x\) 的这些完美数,他们的个位都是 \(d\)

那么解法就很显然了:我们可以将所有二位数的大于等于 \(10\) 的部分全部转移到一个上面去,剩下来的也就是一个个 \(d\) 了,所以我们就可以通过不断减的方式来判断。

(打个比方,\(d=7,x=64=27+37=57+7\)\(d=8,x=18+28+28=8+8+58\))

C题 Nezzar and Symmetric Array (排序,数学构造)

\(T(1\leq T \leq 10^5)\) 组数据。

给定一个长度为 \(2n(n \leq 10^5)\) 数组 \(a\)\(a\) 中各个数字互不相同,而且对于 \(a_i\),必然存在 \(a_j\),使得 \(a_i=-a_j\)

现在构造一个数列 \(d\),其中 \(d_i=\displaystyle\sum_{k=1}^{2n}{|a_i-a_k|}\)

现在给出一个数列 \(d\),判断这个数列是否可以由一个合法的 \(a\) 数列转换而来。

这个题目相当离谱,我写的想砸键盘。

首先我们可以将数列 \(a\) 排一下,正数放在前面,负数放在后面(很显然,不会有 \(0\) 的),排成例如 \([1,2,3,-1,-2,-3]\) 的形式。

在这种情况下,令 \(1 \leq i \leq n\) ,显然有\(d_i=d_{i+n}=\displaystyle\sum_{k=1}^{n}{|a_i-a_k|+|a_i+a_k|}=2*\displaystyle\sum_{k=1}^{n}\max\{a_i,a_k\}\)

如果我们令 \(0 < a_1 < a_2 < a_3 < ...... < a_n,t_i=\frac{d_i}{2}\),那么就有

\(t_1=a_1+a_2+a_3+a_4+......+a_n\)

\(t_2=a_2+a_2+a_3+a_4+......+a_n\)

\(t_3=a_3+a_3+a_3+a_4+......+a_n\)

\(t_4=a_4+a_4+a_4+a_4+......+a_n\)

\(......\)

\(t_n=a_n+a_n+a_n+a_n+......+a_n=na_n\)

当时写题目的时候脑子不太好,第二天才正确找出其中的关系式就离谱:\(t_{k+1}-t_k=k(a_{k+1}-a_k)\)

现在我们已经有了数组\(d\),那我们就可以根据上面的结论,反过来推导 \(a\) 了:

  1. 先排序,如果出现不能完全分成两个对称的部分,或者发现负数,\(0\),奇数啥的,直接退出
  2. 构造成功数组 \(t\) 之后,尝试生成 \(a_n\),如果发现没法整除啥的,直接退出
  3. 之后依次构造\(a_{n-1},a_{n-2},....,a_1\),中途如果出现没法整除,两个数相等,发现 \(0\) 或者负数啥的,直接退出

不想写了,这题够折磨人了,直接上代码:

#include<bits/stdc++.h>
using namespace std;
const int N = 100010;
int n;
long long d[N<<1], t[N], a[N];
bool solve()
{
    scanf("%d", &n);
    for (int i = 1; i <= 2 * n; ++i)
        scanf("%lld", &d[i]);
    sort(d + 1, d + 2*n + 1);
    for (int i = 1; i <= n; ++i)
        if (d[2*i] != d[2*i-1] || d[2*i] % 2) return false;
    for (int i = 1; i <= n; ++i) {
        t[i] = d[2 * i] / 2;
        if (t[i] == t[i-1]) return false;
    }

    if (t[n] % n) return false;
    a[n] = t[n] / n;
    for (int i = n - 1; i >= 1; i--) {
        if ((t[i+1] - t[i]) % i) return false;
        a[i] = a[i + 1] - (t[i+1] - t[i]) / i;
        if (a[i] <= 0) return false;
    }
    return true;
}

int main()
{
    int T;
    scanf("%d", &T);
    while (T--)
        puts(solve() ? "YES" : "NO");
    return 0;
}

D题

等会再补,先给一个刘佬的题解:https://blog.csdn.net/qq_37304699/article/details/113405968

E题 Nezzar and Binary String (线段树)

\(T(1\leq T \leq 2*10^5)\) 组数据。

一开始有一个二进制串 \(s\),长度为 \(n\),一共有 \(q\) 次询问,每个询问给定一个区间 \([L_i,R_i]\),以及两个过程:首先看这个区间里面是否同时含有 \(0\)\(1\),如果有则会立即停止过程;如果都是一样的,则可以修改这个区间里面严格小于区间长度一半的元素,问经过 \(q\) 次询问,且不能中途退出的前提下,是否能把一开始的 \(s\) 变成另外一个二进制串 \(f\)

\(n,q \leq 2*10^5\)

这题如果从正向入手,我们会发现似乎根本无法正向入手(逃

但是如果我们从反向入手的话,我们好像发现,给定一个操作后的 \(01\)\(str\) 和操作区间 \([L,R]\),我们便可以推出操作前的 \(01\) 串,过程如下:

  1. 记录区间上面 \(0\) 的个数 \(cnt0\)\(1\) 的个数 \(cnt1\)
  2. 如果 \(cnt0=cnt1\),我们会发现这玩意没法由原来的串转换而成,因为每次操作所改变的元素的个数不能超过区间长度一半
  3. 反之,如果那个少,我们就将原来的数组全部转变成它所对应的另外一个数(例如 \(0111\) 变成 \(1111\)\(11000\) 变成 \(00000\))。可以证明(很显然的),有且仅有这一种转换方式。

那么事情就很显然了,我们将操作区间反转过来,将 \(f\) 按照逆过来的操作区间来进行上面所说的运算,如果能够一路运算到头,而且最后运算出来的 \(01\) 串和 \(s\) 相同,那么 \(s\) 就是可以转换成 \(f\) 的。

不过尴尬的是,如果在数组上面硬处理,这个复杂度显然是 \(O(n^2)\) 的,这并不能接受。

一看数据规模,再看看这操作:将一个在数组上面的 \(O(n^2)\) 算法优化成 \(O(n\log n)\) ...... 这显然就是数据结构嘛!

这个数据结构需要能够以 \(O(\log n)\) 的复杂度,对一个 \(01\) 串实现这些功能:

  • 查询某区间 \(0\)\(1\) 的个数
  • 将某区间的所有数全部变为 \(0\) 或者 \(1\)

线段树不要太明显,好吗?

好了,这题也成为我在 \(CodeForces\) 上面碰到的第一个复杂算法题,也是我上大学来碰到的少数“知道思路,但是代码不会写”的题目(逃

经过长时间攻关,终于把代码整出来了,给带伙们看一下(就是这样,这些代码的基础还是复制以前的线段树模板,这以后要是参加不让带资料的比赛,我岂不是直接GG?)

#include <bits/stdc++.h>
using namespace std;
const int N = 200010;
int n, q;
char str1[N], str2[N];
int ask_l[N], ask_r[N];
//
struct node {
    int l, r, len;
    int cnt[2], tag[2];
} a[N << 2];
inline int ls(int x) { return x << 1; };
inline int rs(int x) { return x << 1 | 1; };
void pushup(int x) {
    for (int i = 0; i < 2; ++i) {
        a[x].cnt[i] = a[ls(x)].cnt[i] + a[rs(x)].cnt[i];
        a[x].tag[i] = a[ls(x)].tag[i] & a[rs(x)].tag[i];
    }
}
void build(int x = 1, int l = 1, int r = n)
{
    a[x].l = l, a[x].r = r, a[x].len = r - l + 1;
    if (l == r) {
        a[x].cnt[str2[l] - '0']++;
        a[x].tag[str2[l] - '0']++;
        return;
    }
    int mid = (l + r) >> 1;
    build(ls(x), l, mid);
    build(rs(x), mid + 1, r);
    pushup(x);
}
inline void f(int x, int k) {
    a[x].tag[k] = 1;
    a[x].tag[1 - k] = 0;
    a[x].cnt[k] = a[x].len;
    a[x].cnt[1 - k] = 0;
}
inline void pushdown(int x, int k) {
    f(ls(x), k);
    f(rs(x), k);
    //a[x].tag = 0;
}
void update(int l, int r, int val, int x);
int query(int l, int r, int val, int x);
void print() {
    printf("the sequence is:");
    for (int i = 1; i <= n; ++i) {
        printf("%d ", query(i, i, 1, 1));
    }
    puts("");
    return;
}
//

int main()
{
    int T;
    scanf("%d", &T);
    while (T--) {
        //这里我发现,对于每组数据,一定要清理一下存储数组,不然会WA
        //还有,不要直接memset,会T,必须手动算一下memset的大小
        memset(a, 0, sizeof(node) * (4 * n + 10));
        bool ans = true;
        scanf("%d%d", &n, &q);
        scanf("%s", str1 + 1);
        scanf("%s", str2 + 1);
        build();
        for (int i = 1; i <= q; ++i)
            scanf("%d%d", &ask_l[i], &ask_r[i]);
        for (int i = q; i >= 1; i--) {
            int l = ask_l[i], r = ask_r[i];
            int cnt[2];
            cnt[0] = query(l, r, 0, 1);
            cnt[1] = query(l, r, 1, 1);
            if (cnt[0] == cnt[1]) {
                ans = false;
                break;
            }
            if (cnt[0] > cnt[1]) update(l, r, 0, 1);
            else                 update(l, r, 1, 1);
        }
        if (ans) {
            for (int i = 1; i <= n; ++i)
                if (query(i, i, 1, 1) != str1[i] - '0') {
                    ans = false;
                    break;
                }
        }
        puts(ans ? "YES" : "NO");
    }
    return 0;
}
void update(int l, int r, int val, int x)
{
    if (l <= a[x].l && a[x].r <= r)
    {
        a[x].cnt[val] = a[x].len;
        a[x].cnt[1 - val] = 0;
        a[x].tag[val] = 1;
        a[x].tag[1 - val] = 0;
        return;
    }
    if (a[x].tag[0]) pushdown(x, 0);
    if (a[x].tag[1]) pushdown(x, 1);
    int mid = (a[x].l + a[x].r) >> 1;
    if (l <= mid)
        update(l, r, val, ls(x));
    if (r > mid)
        update(l, r, val, rs(x));
    pushup(x);
    return;
}
int query(int l, int r, int val, int x)
{
    if (l <= a[x].l && a[x].r <= r)
        return a[x].cnt[val];
    if (a[x].tag[0]) pushdown(x, 0);
    if (a[x].tag[1]) pushdown(x, 1);
    int mid = (a[x].l + a[x].r) >> 1;
    int ans = 0;
    if (l <= mid)
        ans += query(l, r, val, ls(x));
    if (r > mid)
        ans += query(l, r, val, rs(x));
    return ans;
}

posted @ 2021-01-29 13:31  cyhforlight  阅读(111)  评论(0编辑  收藏  举报