Codeforces Global Round 25

D. Buying Jewels

题解

很容易被误导的题目,其实比较简单。

考虑到店铺越多越不好考虑,因此我们希望用尽可能少的店铺解决问题。这就比较简单了,我们将第一个店铺的珠宝价格设为 \(n - k - 1\),这样经过第一个店铺后,Alice 买了 \(1\) 件珠宝,还剩 \(k - 1\) 个硬币。此时我们将第 \(2\) 个店铺的珠宝价格设为 \(1\),那么 Alice 还会买 \(k - 1\) 件珠宝,此时 Alice 就恰好买了 \(k\) 件珠宝。

不过这种做法需要特判。如果 \(n < k\),即使价格设成 \(1\) 都无法买够 \(k\) 件珠宝,此时无解;如果 \(n = k\),那么只用将第一个店铺的价格设成 \(1\) 即可;如果 \(\displaystyle\frac n2 \leq k < n\),此时第 \(1\) 个店铺无法定价为 \(1\),假设第 \(1\) 个店铺定价为 \(p\),那么 Alice 最多只能买 \(\left\lfloor \displaystyle\frac np \right\rfloor + p\) 件商品,此时只有当 \(n = 2 \times k - 1\)\(p = 2\) 时满足条件,其他情况均无解。

完整代码

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
int T, n, k;
signed main(){
    scanf("%lld", &T);
    while(T--){
        scanf("%lld%lld", &n, &k);
        if(n == k)
            printf("YES\n1\n1\n");
        else if(k * 2 <= n)
            printf("YES\n2\n%lld 1\n", n - k + 1);
        else if(2 * k - 1 == n)
            printf("YES\n2\n2 1\n");
        else
            printf("NO\n");
    }
    return 0;
}

E. No Palindromes

题解

猜结论好题,不过我们现在来严谨推导一下。

首先,如果字符串 \(s\) 全是一种字符,那么一定无解。如果一个字符串 \(s\) 本身就是非回文串,那么直接将一整个串输出即可。于是我们现在只用考虑 \(s\) 是回文串,且 \(s\) 中包含多种字符的情况。

其次,我们找到第一个前缀 \(s[1, k]\) 使得 \(s_1 != s_k\)(不妨设这个前缀为 \(aa \dots ab\)),假设 \(k \leq \displaystyle\frac n2\),那么如果 \(s[k + 1, n]\) 也不是回文串,那么这就是一种合法的划分,直接输出即可。

否则 \(s[k + 1, n]\) 就是一个回文串,而整个字符串也是一个回文串,因此我们可以通过 \(s[1, k]\) 求出 \(s[n - k + 1, n]\),再通过 \(s[n - k + 1, n]\) 求出 \(s[k + 1, 2 \times k]\),如此回环往复,我们就可以求出整个串 \(s\)\(aa \dots abaa \dots ab \dots baa \dots abaa \dots a\)

此时如果每段 \(a\) 的长度都是 \(1\),那么每次划分都有一边是回文串,这是无解的。如果 \(s\)\(aa \dots abaa \dots a\),那么一定有一边会全是 \(a\),这也是无解的,否则直接将 \(k\) 右移一位即可得出一组合法的解。此时我们就发现,一个字符串要么无解,要么一定可以划分成两部分。于是跑一遍 Manacher,再将每个位置作为分界点尝试一遍即可。

完整代码

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N = 3e7 + 9;
int r[N], x, y, n, T;
char tmp[N], s[N];
int main(){
    scanf("%d", &T);
    while(T--){
        x = y = 0;
        scanf("%s", tmp + 1);
        int len = strlen(tmp + 1);
        n = 2 * len - 1;
        s[0] = '@';
        for(int i = 1; i < len; i++){
            s[i * 2 - 1] = tmp[i];
            s[i * 2] = '#';
        }
        s[n] = tmp[len];
        s[n + 1] = '$';
        for(int i = 1; i <= n; i++){
            if(i > y){
                while(s[y + 1] == s[2 * i - (y + 1)]){
                    ++y;
                    x = i;
                }
                r[i] = y - i;
            } else {
                int ip = 2 * x - i;
                if(i + r[ip] < y)
                    r[i] = r[ip];
                else if(i + r[ip] > y)
                    r[i] = y - i;
                else{
                    while(s[y + 1] == s[2 * i - (y + 1)]){
                        ++y;
                        x = i;
                    }
                    r[i] = y - i;
                }
            }
        }
        if(r[len] < len - 1){
            printf("Yes\n1\n");
            for(int i = 1; i <= n; i += 2)
                printf("%c", s[i]);
            printf("\n");
            continue;
        }
        bool flag = false;
        for(int i = 1; i <= n - 2; i += 2){
            if(r[(i + 1) / 2] == (i - 1) / 2)
                continue;
            if(r[(n + i + 2) / 2] == (n - i - 2) / 2)
                continue;
            printf("Yes\n2\n");
            for(int j = 1; j <= n; j += 2){
                printf("%c", s[j]);
                if(j == i)
                    printf(" ");
            }
            printf("\n");
            flag = true;
            break;
        }
        if(!flag)
            printf("No\n");
    }
	return 0;
}

F. Inversion Composition

题解

我们设 \(pos_i\) 表示排列 \(p\) 中值 \(i\) 的位置,我们又知道 \(p_i\) 表示 \(i\) 位置的值,因此 \(p_{pos_i} = i\)

假设排列 \(q\) 中存在一个 \(i < j, q_i < q_j\),如果 \(pos_j < pos_i\),那么 \(q_{p_{pos_j}} = q_j > q_i = q_{p_{pos_i}}\),此时就会对答案造成 \(1\) 的贡献;如果 \(pos_j > pos_i\),此时就不会造成贡献。假设排列 \(p\) 中存在一个 \(i < j, q_i > q_j\),如果 \(pos_j < pos_i\) 此时就会对答案造成 \(1\) 的贡献;如果 \(pos_j > pos_i\),此时就会对答案造成 \(2\) 的贡献。

此时我们注意到排列 \(p\) 中的一个逆序对一定会对答案造成 \(1\) 的贡献,而一个顺序对会造成 \(0\) 或者 \(2\) 的贡献。于是我们先计算出 \(p\) 中的逆序对数量,将 \(k\) 减去这个值。此时 \(k\) 如果为奇数,小于 \(0\) 或大于顺序对的两倍就无解,否则一定存在一种构造方案(为了方便描述,下文的 \(k\) 为减去 \(p\) 中逆序对数量后除以 \(2\) 的值)。

注意到对于一对 \(i < j, pos_i < pos_j\),如果 \(q_i > q_j\),那么一定会造成 \(1\) 的贡献。于是我们从左往右,从大到小填数。假设我们枚举到了第 \(i\) 个位置,\(i\) 之前有 \(c\) 个位置的 \(pos > pos_i\),如果 \(2 \times c < k\),那么直接将 \(k\) 减去 \(c\);否则我们找到最小的 \(j\) 满足 \(j\) 之前有 \(k\) 个位置的 \(pos > pos_i\),将 \(q_{1 \sim j}\) 赋值成 \(i \sim i - j + 1\),将 \(q_{j + 1 \sim i - 1}\) 赋值成 \(i - j - 1 \sim 1\),此时答案就恰好为 \(k\),于是我们不能让 \(i\) 之后填的数造成贡献,因此我们将 \(q_{i + 1 \sim n}\) 赋值成 \(q_{i + 1 \sim n}\) 就可以了。时间复杂度 \(\mathcal O(n \log n)\)

完整代码

点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 3e5 + 9;
int t[N], p[N], pos[N], n, k, T;
int lowbit(int x){
    return x & -x;
}
void insert(int i, int x){
    for(; i <= n; i += lowbit(i))
        t[i] += x;
}
int query(int i){
    int res = 0;
    for(; i > 0; i -= lowbit(i))
        res += t[i];
    return res;
}
signed main(){
    scanf("%lld", &T);
    while(T--){
        scanf("%lld%lld", &n, &k);
        for(int i = 1; i <= n; i++)
            t[i] = pos[i] = 0;
        for(int i = 1; i <= n; i++){
            scanf("%lld", &p[i]);
            pos[p[i]] = i;
            k -= i - 1 - query(p[i]);
            insert(p[i], 1);
        }
        if(k < 0 || k % 2 == 1){
            printf("NO\n");
            continue;
        }
        for(int i = 1; i <= n; i++)
            t[i] = 0;
        k /= 2;
        bool flag = false;
        for(int i = 1; i <= n; i++){
            int now = query(pos[i]);
            insert(pos[i], 1);
            if(k <= now){
                flag = true;
                printf("YES\n");
                if(k == 0){
                    for(int j = i; j >= 1; j--)
                        printf("%lld ", j);
                    for(int j = i + 1; j <= n; j++)
                        printf("%lld ", j);
                    printf("\n");
                    break;
                }
                int cnt = 0;
                for(int j = 1; j <= i; j++){
                    if(pos[j] < pos[i])
                        cnt++;
                    if(cnt == k){
                        for(int l = i; l >= i - j + 1; l--)
                            printf("%lld ", l);
                        for(int l = i - j - 1; l >= 1; l--)
                            printf("%lld ", l);
                        printf("%lld ", i - j);
                        for(int l = i + 1; l <= n; l++)
                            printf("%lld ", l);
                        printf("\n");
                        break;
                    }
                }
                break;
            } else
                k -= now;
        }
        if(!flag)
            printf("NO\n");
    }
    return 0;
}

G. Clacking Balls

题解

对于这类的停时问题,考虑给一个局面构造一个势能函数。如果 Alice 每进行一次操作,都会使这个势能函数减少 \(1\),那么我们只用将初始局面的势能减去终止局面的势能,就可以得出答案。

在这道题目中,我们考虑用相邻两个球之间的距离来设计势能函数。设 \(d_i = a_{i \bmod n + 1} - a_i\),那么当前局面的势能就是 \(\displaystyle\sum_{i = 1}^n f(d_i)\)。考虑到下一步操作有 \(n\) 种方法,那么势能的期望减少量就是 \(\displaystyle\frac 1n \displaystyle\sum_{i = 1}^n f(d_i) + f(d_{i \bmod n + 1}) - f(d_i - 1) - f(d_{i \bmod n + 1} + 1)\),根据刚才的思路,我们希望这个变化量为 \(1\)

我们设 \(g(x) = f(x + 1) - f(x)\),那么 \(n = \displaystyle\sum_{i = 1}^n g(d_i - 1) - g(d_{i \bmod n} + 1) = \sum_{i = 1}^n g(d_i - 1) - \sum_{i = 1}^n g(d_{i \bmod n} + 1)\)。注意到一个数 \(d_i\) 会对前一项造成 \(g(d_i - 1)\) 的贡献,对后一项造成 \(g(d_i)\) 的贡献,因此等式可以改写成 \(n = \displaystyle\sum_{i = 1}^n g(d_i - 1) - g(d_i)\)

通过比较人类智慧的观察,我们发现如果 \(g(x - 1) - g(x) = \displaystyle\frac nm x\) 的话,\(\displaystyle\sum_{i = 1}^n g(d_i - 1) - g(d_i) = \frac nm \sum_{i = 1}^n d_i = n\),是满足条件的。此时假设 \(g(0) = 0\),那么可以发现 \(g(x) = - \displaystyle\frac nm \binom{x + 1}{2}\) 是一个合法的 \(g\),那么倒推出 \(f(x) = - \displaystyle\frac nm \binom{x + 1}{3}\)。于是我们用初始状态的势能,减去终止局面的势能 \(f(m)\),就是答案了。

完整代码

点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 3e5 + 9, MOD = 1e9 + 7;
int qpow(int a, int b){
    int res = 1;
    while(b > 0){
        if(b & 1)
            res = res * a % MOD;
        a = a * a % MOD;
        b >>= 1;
    }
    return res;
}
int a[N], T, n, m, invm, inv6;
int f(int x){
    return MOD - n * invm % MOD * (x + 1) % MOD * x % MOD * (x - 1) % MOD * inv6 % MOD;
}
signed main(){
    inv6 = qpow(6, MOD - 2);
    scanf("%lld", &T);
    while(T--){
        int ans = 0;
        scanf("%lld%lld", &n, &m);
        invm = qpow(m, MOD - 2);
        for(int i = 1; i <= n; i++)
            scanf("%lld", &a[i]);
        sort(a + 1, a + n + 1);
        for(int i = 1; i < n; i++)
            ans = (ans + f(a[i + 1] - a[i])) % MOD;
        ans = (ans + f(m - (a[n] - a[1]))) % MOD;
        printf("%lld\n", (ans - f(m) + MOD) % MOD);
    }
    return 0;
}

H. Thanos Snap

题解

首先,我们可以二分这个最大得分 \(mid\),将 \(a\) 序列中 \(\geq mid\) 的数设成 \(1\),其它数设成 \(0\),那么现在原问题就变成了进行 \(t\) 轮操作后,最终得到的序列中是否有 \(1\)。我们后续将得到的序列中一定有 \(1\) 称为有解

我们考虑如果进行了 \(dep\) 轮操作后问题有解,那么一个必要条件就是 \(1\) 的数量大于等于 \(2^{t - dep}\),不然 Bob 每次选择 \(1\) 数量较少的那一边往下递归,一定会让最终得到的序列全为 \(0\)。假设我们可以进行无数次交换操作,那么这个必要条件就变成了充要条件。于是我们考虑在交换次数上设计算法。

考虑到每次我们会将整个序列对半分开,于是我们考虑类似线段树的形式进行 DP。设 \(dp_i\) 表示线段树上的第 \(i\) 个节点表示的区间,为了让这个区间有解,最少需要将多少个 \(1\) 交换到第 \(i\) 个节点表示的区间。从下往上 DP,我们发现如果要让 \([l, r]\) 有解,必须让 \([l, mid]\)\([mid + 1, r]\) 也有解,而操作 \([l, r]\) 区间会增加 \(1\) 次交换的机会,因此 \(dp_i\) 至少为 \(dp_{i \times 2} + dp_{i \times 2 + 1} - 1\)

\([l, r]\) 有解还需要满足我们一开始推出的必要条件,就是 \([l, r]\) 中至少需要 \(2^{t - dep}\)\(1\),假设这个区间已有 \(cnt\)\(1\),那么还需要将 \(2^{t - dep} - cnt\)\(1\) 交换进这个区间,因此 \(dp_i\) 还要对这个值取 \(\max\)。此时我们在 \(\mathcal O(n \log^2 n)\) 的时间复杂度内解决了这个问题。

完整代码

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N = 2e6 + 9;
int t[N << 2], dp[N << 2], sum[N], a[N], tar, T, n, k;
void build(int id, int l, int r, int dep){
    t[id] = sum[r] - sum[l - 1];
    if(dep == tar){
        dp[id] = (t[id] == 0);
        return;
    }
    int mid = (l + r) >> 1;
    build(id << 1, l, mid, dep + 1);
    build(id << 1 | 1, mid + 1, r, dep + 1);
    dp[id] = max(max(0, dp[id << 1] + dp[id << 1 | 1] - 1), (1 << (tar - dep)) - t[id]);
    return;
}
int main(){
    scanf("%d", &T);
    while(T--){
        scanf("%d", &k);
        n = (1 << k);
        for(int i = 1; i <= n; i++)
            scanf("%d", &a[i]);
        for(int i = 1; i <= k; i++){
            tar = i;
            int l = 1, r = N;
            while(l <= r){
                int mid = (l + r) >> 1;
                for(int j = 1; j <= n; j++)
                    sum[j] = sum[j - 1] + (a[j] >= mid);
                build(1, 1, n, 0);
                if(dp[1] <= 0)
                    l = mid + 1;
                else
                    r = mid - 1;
            }
            printf("%d ", l - 1);
        }
        printf("\n");
    }
    return 0;
}
posted @ 2025-10-09 21:27  Orange_new  阅读(8)  评论(1)    收藏  举报