[CF1902] Educational Codeforces Round 159 A~E 题解

发布时间 2023-12-04 23:14:08作者: MoyouSayuki

[CF1902] Educational Codeforces Round 159 A~E 题解

A. Binary Imbalance

很快观察到如果有不同的相邻元素,那么一定有解,意味着如果全是 1 无解,其他有解

B. Getting Points

题面很长,可以发现,最好的偷懒方式一定是把所有的课都拖到最后几天上(真实),可以简单调整证明这样是不劣的,最后几天上的课有三种课:

  1. 完成两个任务
  2. 完成一个任务
  3. 不做任务

推推式子就出了。

void work() {
    int n, p, a, b;
    cin >> n >> p >> a >> b;
    int t = (n - 1) / 7 + 1;
    int cnt = t / 2;
    int ans = 0;
    if(cnt * (a + 2 * b) >= p) {
        cout << n - ((p + a + 2 * b - 1) / (a + 2 * b)) << '\n';
        return ;
    }
    p -= cnt * (a + 2 * b);
    ans += cnt;
    if((t % 2 == 1) && a + b >= p) {
        cout << n - 1 - cnt << '\n';
        return ;
    }
    else if((t % 2 == 1)) {
        ans ++;
        p -= a + b;
    }
    cout << n - ans - ((p + a - 1) / a) << '\n';
    
    return ;
}

C. Insert and Equalize

考虑如果不加入新元素是什么情况。

那么最后一定会等于最大数,否则可以同时减去 \(x\) 这样更优。

那么这个 \(x\) 就必须整除所有元素和最大数的差,取个 \(\gcd\) 即可。

考虑加入新元素,那么显然不应该使 \(x\) 变小,所以只有两种情况。

  1. \(a_{n + 1} > a_n\) 此时 \(a_{n + 1} = a_n + x\)
  2. \(a_{n + 1} < a_n\),此时 \(a_{n + 1} = a_n - kx\)\(k\) 是使得 \(a_{n + 1}\) 不重复的最小的正整数。

\(\min\) 即可。

void work() {
    cin >> n;
    s.clear();
    for(int i = 1; i <= n; i ++) cin >> a[i], s.insert(a[i]);
    if(n == 1) { // 要读完 a1 再return 不然会吃两发罚时
        cout << 1 << '\n';
        return ;
    }
    sort(a + 1, a + n + 1);
    int ans = 0, d = a[2] - a[1];
    for(int i = 3; i <= n; i ++) {
        d = gcd(d, a[i] - a[i - 1]);
    }
    for(int i = 1; i <= n; i ++)
        ans += (a[n] - a[i]) / d;
    int pos = 1e9;
    for(int i = 1; i <= n + 1; i ++)
        if(s.find(a[n] - i * d) == s.end()) {
            pos = i;
            break;
        }
    cout << ans + min(n, pos) << '\n';
    return ;
}

D. Robot Queries

先模拟处理出 \(p_i\) 表示第 \(i\) 次执行后机器人坐标。

观察到反转操作实际上是把所有原路径上的点关于 \(p_l, p_r\) 的中点做对称,不妨把查询的 \((x, y)\) 关于中点做对称再直接查询原路径上的点,由对称性,这样是等价的。

所以我们现在需要维护在 \([l, r]\) 时刻,\((x, y)\) 是否被经过。

观察到值域比较小,可以使用 vector 维护每一个点在哪些时刻被经过,查询的时候二分 \(l, r\) 即可。

vector<int> c[N];
bool calc(int l, int r, int t) {
    int lp = lower_bound(c[t].begin(), c[t].end(), l) - c[t].begin(), rp = upper_bound(c[t].begin(), c[t].end(), r) - c[t].begin() - 1;
    return lp <= rp;
}

signed main() {
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    cin >> n >> q >> s;
    c[id({0, 0})].push_back(0);
    for(int i = 0, x = 0, y = 0; i < n; i ++) {
        if(s[i] == 'U') y ++;
        if(s[i] == 'D') y --;
        if(s[i] == 'L') x --;
        if(s[i] == 'R') x ++;
        p[i + 1] = {x, y};
        c[id({x, y})].push_back(i + 1);
    }
    for(int i = 1, x, y, l, r; i <= q; i ++) {
        cin >> x >> y >> l >> r;
        if(calc(0, l - 1, id({x, y})) || calc(r, n, id({x, y})) || calc(l, r - 1, id({p[l - 1].x + p[r].x - x, p[l - 1].y + p[r].y - y})))
            cout << "YES\n";
        else
            cout << "NO\n";
    }

    return 0;
}

E. Collapsing Strings

首先正难则反方便做,用总字符串长度减去被消掉的长度,观察到 \(C(a, b)\) 函数的本质是求 \(LCP(rev_a, b)\)\(rev_a\) 表示 \(a\) 的反转。

看到前缀考虑 Trie,插入所有字符串的反转,在插入的路径上增加 1 的贡献,意味着多匹配了一个字符,之后枚举 \(b\),在 Trie 上跑匹配,统计路径上所有的贡献。

最后需要 \(\times 2\),因为这样只会统计 \(b\) 作为第二个字符串的贡献。

#include <algorithm>
#include <cstring>
#include <iostream>
#include <queue>
// #define int long long
#define x first
#define y second
using namespace std;
typedef pair<int, int> PII;
typedef long long ll;
const int N = 1e6 + 10, M = 2e6 + 10;

int tr[M][26], cnt[M], idx;
void update(string s) {
    int u = 0;
    for(int i = (int)s.size() - 1; i >= 0; i --) {
        int c = s[i] - 'a';
        if(!tr[u][c]) tr[u][c] = ++ idx;
        u = tr[u][c]; 
        cnt[u] ++;
    }
}
int query(string s) {
    int u = 0, ans = 0;
    for(int i = 0; i < s.size(); i ++) {
        int c = s[i] - 'a';
        if(tr[u][c]) u = tr[u][c];
        else break;
        ans += cnt[u];
    }
    return ans;
}
int n;
string s[N];
signed main() {
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    cin >> n;
    for(int i = 1; i <= n; i ++) cin >> s[i], update(s[i]);
    long long ans = 0;
    for(int i = 1; i <= n; i ++) ans += s[i].size();
    ans = ans * 2 * n;
    for(int i = 1; i <= n; i ++)
        ans -= 2 * query(s[i]);
    cout << ans << '\n';

    return 0;
}

总结

B 一开始没看懂题,切慢了一点,不过亮点是把所有情况都讨论了一发过,C 交了两发罚时,以后注意一下特判的时候要保证所有的数据都读完了。

E 莽对了一发过,但是 D 观察到中点对称的性质之后没有想到怎么维护,看了 \(\text{\color{black}g\color{red}yh20}\) 大佬的代码之后恍然大悟。