Codeforces Round 1016 (Div. 3)

A. Ideal Generator

我们称一个由 \(k\) 个正整数组成的数组 \(a\) 为回文数组,如果满足

\[[a_1,a_2,\dots,a_k]=[a_k,a_{k-1},\dots,a_1]. \]

例如,数组 \([1,2,1]\)\([5,1,1,5]\) 是回文数组,而数组 \([1,2,3]\)\([21,12]\) 则不是。
我们称一个数 \(k\) 为“理想生成器”,如果任意整数 \(n\)\(n \ge k\))都可以表示为一个长度恰为 \(k\) 的回文数组元素之和。该数组的每个元素都必须大于 \(0\)
例如,数 \(1\) 是一个理想生成器,因为任何自然数 \(n\) 都可以通过数组 \([n]\) 来生成。然而,数 \(2\) 不是理想生成器 —— 没有长度为 \(2\) 的回文数组的元素之和等于 \(3\)
判断给定的数 \(k\) 是否为理想生成器。
\(k \leq 1000\)

由于 \(a_i = a_{k-i+1}\),当 \(k\) 为偶数时数组和不能为奇数,为奇数时是理想生成器。

代码
#include <bits/stdc++.h>

using namespace std;

typedef pair<int, int> pii;
typedef pair<int, pair<int, int> > piii;
typedef long long ll;
const int N = 2000086, MOD = 1e9 + 7, INF = 0x3f3f3f3f;
ll res;
int n, m, cnt, w[N];

int main() {
    int T;
    cin >> T;
    while (T--) {
        cin >> n;
        puts(n & 1 ? "YES" : "NO");
    }
    return 0;
}

B. Expensive Number

正整数 \(n\) 的 “代价” 定义为 \(n\) 除以它的各位数字之和的结果。
例如,数字 \(104\) 的代价为 \(\frac{104}{1+0+4}=\frac{104}{5}=20.8\),而数字 \(111\) 的代价为 \(\frac{111}{1+1+1}=\frac{111}{3}=37\)
现给定一个不含前导零的正整数 \(n\)。你可以从数字 \(n\) 中移除任意数量的数字(可以不移除任何数字),要求剩下的数字至少有一位且严格大于零。注意,剩下的数字不能重新排列,因此,最终得到的数字可能会有前导零。
\(n \leq 10^{100}\)

最小代价为 \(1\),最优做法为保留最低位的非 \(0\) 数,移除掉其余所有非 \(0\) 数以及数位比该位高的 \(0\)

代码
#include <bits/stdc++.h>

using namespace std;

typedef pair<int, int> pii;
typedef pair<int, pair<int, int> > piii;
typedef long long ll;
const int N = 2000086, MOD = 1e9 + 7, INF = 0x3f3f3f3f;
ll res;
int n, m, cnt, w[N];

char s[N];

int main() {
    int T;
    cin >> T;
    while (T--) {
        scanf("%s", s + 1);
        n = strlen(s + 1);
        int c = 0;
        bool flag = 0;
        for (int i = n; i; i--) {
            flag |= s[i] != '0';
            c += flag && s[i] == '0';
        }
        printf("%d\n", n - c - 1);
    }
    return 0;
}

C. Simple Repetition

帕沙热爱素数!在他再次尝试寻找生成素数的新方法时,他对互联网上发现的一种算法产生了兴趣:
为了得到一个新数字 \(y\),将数字 \(x\) 的十进制表示(不含前导零)重复 \(k\) 次。
例如,当 \(x=52\)\(k=3\) 时,得到 \(y=525252\);当 \(x=6\)\(k=7\) 时,得到 \(y=6666666\)
\(x \leq 10^9,1 \leq k \leq 7\)

如果 \(x\) 不为 \(1\)\(k\) 大于 \(1\),此时 \(x\) 必定是 \(y\) 的因数。当 \(x\)\(1\) 时,只有在 \(k\)\(2\)\(y\) 为质数。

代码
#include <bits/stdc++.h>

using namespace std;

typedef pair<int, int> pii;
typedef pair<int, pair<int, int> > piii;
typedef long long ll;
const int N = 2000086, MOD = 1e9 + 7, INF = 0x3f3f3f3f;
ll res;
int n, m, cnt, w[N];

int main() {
    int T;
    cin >> T;
    while (T--) {
        cin >> n >> m;
        if (n == 1 && m == 2) {
            puts("YES");
        } else if (m > 1) {
            puts("NO");
        } else {
            bool flag = n == 1;
            for (ll i = 2; i * i <= n; i++)
                if (n % i == 0) {
                    flag = 1;
                }
            puts(flag ? "NO" : "YES");
        }
    }
    return 0;
}

D. Skibidi Table

瓦迪姆喜欢用整数填充方形表格。但今天他为了好玩,想出了一个有趣的填表方法!
例如,我们取一个 \(2\times 2\) 的表格,行从上到下编号,列从左到右编号。按照以下方式填数:在左上角放 \(1\),在右下角放 \(2\),在左下角放 \(3\),在右上角放 \(4\)。这就是他的填表方法的全部规则!
幸运的是,瓦迪姆拥有一个大小为 \(2^n\times 2^n\) 的表格。他计划将表格填入从 \(1\)\(2^{2n}\) 的整数,并且按照递增顺序填入。为了填充如此大的表格,瓦迪姆会先将其分成四个大小相等的子方格,依次按如下顺序填充:

  1. 左上子表格;

  2. 右下子表格;

  3. 左下子表格;

  4. 右上子表格。

每个较小的表格在填充时又会被继续划分成更小的子表格,直至划分到 \(2\times 2\) 的表格,此时按照上述描述的顺序进行填充。
现在,瓦迪姆迫不及待地想开始填表,但他有 \(q\) 个问题,这些问题分为两种类型:

  • 第一种类型:询问位于第 \(x\) 行、第 \(y\) 列的单元格中数字是多少;
  • 第二种类型:询问数字 \(d\) 位于哪个单元格(给出坐标)。

请帮助回答瓦迪姆的问题。
\(n \leq 30, q \leq 20000\)

对于第一种查询,维护当前坐标范围左上角的数字及坐标范围,根据 \(x,y\) 递归到四个子表格之一,对于第二种查询,维护当前坐标范围及剩余的数字 \(d\),根据 \(d\) 递归到四个子表格之一。

代码
#include <bits/stdc++.h>

using namespace std;

typedef pair<int, int> pii;
typedef pair<int, pair<int, int> > piii;
typedef long long ll;
const int N = 2000086, MOD = 1e9 + 7, INF = 0x3f3f3f3f;
ll res;
int n, m, cnt, w[N];

void cal(ll c, int& x, int& y, int d) {
    if (!c) return;
    if (c < (ll)d * d) return cal(c, x, y, d >> 1);
    c -= (ll)d * d;
    if (c < (ll)d * d) return cal(c, x += d, y += d, d >> 1);
    c -= (ll)d * d;
    if (c < (ll)d * d) return cal(c, x += d, y, d >> 1);
    c -= (ll)d * d;
    return cal(c, x, y += d, d >> 1);
}

ll cal(ll x, ll y, ll px, ll py, ll d) {
    ll dx = px + d, dy = py + d;
    if (!d) return 0;
    if (x < dx && y < dy) return cal(x, y, px, py, d >> 1);
    if (x >= dx && y >= dy) return d * d + cal(x, y, dx, dy, d >> 1);
    if (x >= dx) return d * d * 2 + cal(x, y, dx, py, d >> 1);
    return d * d * 3 + cal(x, y, px, dy, d >> 1);
}

void solve() {
    while (m--) {
        char op[2];
        ll x, y;
        scanf("%s", op);
        if (op[0] == '-') {
            scanf("%lld%lld", &x, &y);
            x--, y--;
            printf("%lld\n", cal(x, y, 0ll, 0ll, 1 << (n - 1)) + 1);
        } else {
            scanf("%lld", &x);
            int a = 0, b = 0;
            cal(x - 1, a, b, 1 << (n - 1));
            printf("%d %d\n", a + 1, b + 1);
        }
    }
}

int main() {
    int T;
    cin >> T;
    while (T--) {
        cin >> n >> m;
        solve();
    }
    return 0;
}

E. Min Max MEX

给定一个长度为 \(n\) 的数组 \(a\) 以及一个数字 \(k\)
一个子数组被定义为数组中由一个或多个连续元素组成的序列。你需要将数组 \(a\) 划分为 \(k\) 个不重叠的子数组 \(b_1, b_2, \dots, b_k\),要求这些子数组的并集等于整个数组 \(a\)。此外,你需要最大化一个值 \(x\),其中 \(x\) 等于所有子数组的 MEX 值的最小值,即

\[x = \min\{\text{MEX}(b_i) \mid 1\le i\le k\}. \]

\(1 \leq k \leq n \leq 2 \times 10^5\)

显然 \(x\) 可以二分,考虑在固定了 \(x\) 后如何判断是否可行。对于两个相邻的子数组,它们合并后的 \(\operatorname{MEX}\) 一定不小于合并前的 \(\operatorname{MEX}\),那么如果存在一种方式将 \(a\) 划分成 \(y(y>1)\) 段且每一段的 \(\operatorname{MEX}\) 都不小于 \(x\),则一定存在一种方式能将 \(a\) 划分成 \(y - 1\) 段且每一段的 \(\operatorname{MEX}\) 都不小于 \(x\),因此应该尽可能地将数组分成更多段。从左到右遍历数组,维护当前子数组的 \(\operatorname{MEX}\),当 \(\operatorname{MEX} \geq x\) 时以当前位置为当前子数组的结尾,最后判断子数组数量是否大于等于 \(k\)

代码
#include <bits/stdc++.h>

using namespace std;

typedef pair<int, int> pii;
typedef pair<int, pair<int, int> > piii;
typedef long long ll;
const int N = 2000086, MOD = 1e9 + 7, INF = 0x3f3f3f3f;
ll res;
int n, m, cnt, w[N];

bool check(int mid) {
    vector<bool> st(mid + 2);
    int c = 0, cur = 0;
    for (int i = 1; i < n; i++) {
        if (w[i] <= mid && !st[w[i]]) {
            cur++, st[w[i]] = 1;
            if (cur == mid) {
                cur = 0, c++;
                fill(st.begin(), st.end(), 0);
            }
        }
    }
    return c >= m;
}

void solve() {
    int l = 0, r = n;
    while (l < r) {
        int mid = l + r + 1 >> 1;
        if (check(mid)) l = mid;
        else r = mid - 1;
    }
    printf("%d\n", r);
}

int main() {
    int T;
    cin >> T;
    while (T--) {
        cin >> n >> m;
        for (int i = 1; i < n + 1; i++) scanf("%d", w + i);
        solve();
    }
    return 0;
}

F. Hackers and Neural Networks

黑客们再次尝试使用神经网络的输出制造有趣的短语。这一次,他们想要获得一个长度为 \(n\) 的字符串数组 \(a\)
起初,他们有一个长度为 \(n\) 的数组 \(c\),其中全部元素都是空白,用符号 \(∗\) 表示。也就是说,如果 \(n=4\),那么最初 \(c=[∗,∗,∗,∗]\)
黑客们可以使用 \(m\) 个神经网络,每个神经网络都有自己版本的答案——一个长度为 \(n\) 的字符串数组 \(b_i\)
黑客们试图通过以下操作将数组 \(c\) 转换为数组 \(a\)

  • 选择一个神经网络 \(i\),该神经网络将对数组 \(c\) 进行下一次操作:它会随机选择一个空白位置(例如位置 \(j\)),并将 \(c_j\) 替换为 \(b_{i,j}\)
    例如,如果选择了第一个神经网络,且当前 \(c=[∗,\text{“like”},∗]\),而 \(b_1=[\text{“I”},\text{“love”},\text{“apples”}]\),那么在使用第一个神经网络进行操作后,\(c\) 可能变为 \([\text{“I”},\text{“like”},∗]\)\([∗,\text{“like”},\text{“apples”}]\)
  • 选择位置 \(j\) 并将 \(c_j\) 替换为空白。

不幸的是,由于黑客们访问神经网络的方式,他们只能在所有操作完成之后才能看到修改后的数组 \(c\),因此他们必须提前指定整个操作序列。
然而,神经网络的随机行为可能导致预期的数组永远无法获得,或者需要进行过多的操作才能获得。
因此,黑客们指望你来帮忙选择一系列操作,使得能够在最少的操作次数内保证获得数组 \(a\)
更形式化地说,如果存在一系列操作可以保证将数组 \(c\) 转换为数组 \(a\),那么在所有这样的操作序列中,找到操作次数最少的那个,并输出其中的操作数。
如果不存在能将数组 \(c\) 转换为数组 \(a\) 的操作序列,则输出 \(-1\)
\(1 \leq n,m \leq 500,|a_i|,|b_{i,j}| \leq 10\)

对于一开始所有元素为空的时候,最坏情况是每一次操作都会随机到一个 \(a_i \neq b_{j,i}\) 的位置,那么最优操作应该是先选择一个同位置字符串与 \(a\) 相等数量最多的一个 \(b\) 并进行 \(n\) 次一操作,这样操作后,对于 \(c\) 中剩下每一个依旧与 \(a_i\) 不相等的位置 \(i\),先进行二操作,再随便找一个在该位置的字符串与 \(a_i\) 相等的 \(b\) 进行一次一操作即可。

代码
#include <bits/stdc++.h>

using namespace std;

typedef pair<int, int> pii;
typedef pair<int, pair<int, int> > piii;
typedef long long ll;
const int N = 2000086, MOD = 1e9 + 7, INF = 0x3f3f3f3f;
ll res;
int n, m, cnt, w[N];

void solve() {
    cin >> n >> m;
    vector<string> a(n + 10);
    vector<bool> st(n + 10);
    vector<vector<string> > b(m + 2, vector<string>(n + 2));
    for (int i = 1; i < n + 1; i++) cin >> a[i];
    for (int i = 1; i < m + 1; i++)
        for (int j = 1; j < n + 1; j++)
            cin >> b[i][j], st[j] = st[j] | (b[i][j] == a[j]);
    for (int i = 1; i < n + 1; i++)
        if (!st[i]) {
            puts("-1");
            return;
        }

    int top = 0;
    for (int i = 1; i < m + 1; i++) {
        int c = 0;
        for (int j = 1; j < n + 1; j++) c += a[j] == b[i][j];
        top = max(top, c);
    }
    if (!top) {
        puts("-1");
        return;
    }
    printf("%d\n", n * 3 - 2 * top);
}

int main() {
    ios::sync_with_stdio(false); cin.tie(nullptr);
    int T;
    cin >> T;
    while (T--) solve();
    return 0;
}

G. Shorten the Array

给定一个长度为 \(m\) 的数组 \(b\),其“美丽值”定义为所有可能的下标对 \(1 \le i \le j \le m\) 中,\(b_i \oplus b_j\) 的最大值,其中 \(x \oplus y\) 表示数字 \(x\)\(y\) 的按位异或。我们用 \(f(b)\) 表示数组 \(b\) 的美丽值。
当数组 \(b\) 满足 \(f(b) \ge k\) 时,称其为美丽的数组。
最近,Kostya 从商店购买了一个长度为 \(n\) 的数组 \(a\)。他觉得这个数组太长了,所以计划从中截取一个美丽的子数组。也就是说,他想选择下标 \(l\)\(r\)\(1 \le l \le r \le n\)),使得子数组 \(a_l, a_{l+1}, \dots, a_r\) 是美丽的。该子数组的长度为 \(r - l + 1\)。需要注意的是,整个数组 \(a\) 也被视为一个子数组(当 \(l=1\)\(r=n\) 时)。
你的任务是找出数组 \(a\) 中最短的美丽子数组的长度。如果不存在美丽的子数组,则输出 \(-1\)
\(n \leq 2 \times 10^5,0 \leq k,a_i \leq 10^9\)

写了个可持久化字典树板子。

代码
#include <bits/stdc++.h>

using namespace std;

typedef pair<int, int> pii;
typedef pair<int, pair<int, int> > piii;
typedef long long ll;
const int N = 2000086, MOD = 1e9 + 7, INF = 0x3f3f3f3f;
ll res;
int n, m, cnt, w[N];

struct Trie {
  int cnt, rt[N], ch[N * 6][2], val[N * 6];

  void insert(int o, int lst, int v) {
    for (int i = 30; i >= 0; i--) {
      val[o] = val[lst] + 1;
      if ((v & (1 << i)) == 0) {
        if (!ch[o][0]) ch[o][0] = ++cnt;
        ch[o][1] = ch[lst][1];
        o = ch[o][0];
        lst = ch[lst][0];
      } else {
        if (!ch[o][1]) ch[o][1] = ++cnt;
        ch[o][0] = ch[lst][0];
        o = ch[o][1];
        lst = ch[lst][1];
      }
    }
    val[o] = val[lst] + 1;
  }

  int query(int o1, int o2, int v) {
    int ret = 0;
    for (int i = 30; i >= 0; i--) {
      int t = ((v & (1 << i)) ? 1 : 0);
      if (val[ch[o1][!t]] - val[ch[o2][!t]])
        ret += (1 << i), o1 = ch[o1][!t],
                         o2 = ch[o2][!t];
      else
        o1 = ch[o1][t], o2 = ch[o2][t];
    }
    return ret;
  }

  void clear() {
    for (int i = 0; i <= cnt; i++) ch[i][0] = ch[i][1] = val[i] = 0;
    cnt = 0;
  }
} st;

void solve() {
    if (!m) {
        puts("1");
        return;
    }
    st.clear();
    for (int i = 1; i < n + 1; i++) st.rt[i] = ++st.cnt, st.insert(st.rt[i], st.rt[i - 1], w[i]);

    res = INF;
    for (int i = 2; i < n + 1; i++) {
        if (st.query(st.rt[i - 1], st.rt[0], w[i]) < m) continue;
        int l = 1, r = i - 1;
        while (l < r) {
            int mid = l + r + 1 >> 1;
            if (st.query(st.rt[i - 1], st.rt[mid - 1], w[i]) >= m) l = mid;
            else r = mid - 1;
        }
        res = min(res, (ll)i - l + 1);
    }
    printf("%d\n", res == INF ? -1 : res);
}

int main() {
    int T;
    cin >> T;
    while (T--) {
        cin >> n >> m;
        for (int i = 1; i < n + 1; i++) scanf("%d", w + i);
        solve();
    }
    return 0;
}
posted @ 2025-04-09 02:06  __insomnium  阅读(58)  评论(0)    收藏  举报