• 博客园logo
  • 会员
  • 周边
  • 新闻
  • 博问
  • 闪存
  • 众包
  • 赞助商
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录

yumiym765

  • 博客园
  • 联系
  • 订阅
  • 管理

公告

View Post

牛客周赛 Round 110 题解

牛客周赛 Round 110

A 小苯的数字染色

​ 手玩发现只有 \(n\) 为 \(1\) 不行。

void solve(){
    int n;
    cin >> n;
    if(n == 1){
        cout << "NO\n";
    }else{
        cout << "YES\n";
    }
}

B 小苯的数组重排

​ 发现无论怎么排序,\([a_2,a_3,\dots ,a_{n-1}]\) 总是计算了两次,只有 \(a_1\) 和 \(a_n\) 计算了一次,我们只需要将最小的值作为 \(a_1\) 和 \(a_n\) 即可。

​ 可以两次循环找最小值和次小值,时间复杂度为 \(O(n)\);也可直接排序寻找,时间复杂度为 \(O(nlogn)\),题目数据可以通过。

void solve(){
    int n;
    cin >> n;
    vector<int> a(n);
    ll sum = 0;
    for(int i = 0;i < n;i++){
        cin >> a[i];
        sum += 2 * a[i];
    }
    sort(a.begin(), a.end());
    sum -= a[0] + a[1];
    cout << sum << "\n";
}

C 小苯的麦克斯

​ 如果能只选一个数,那么只选一个最大值一定是最优解,因为此处 \(MAX\) 最大,\(MEX\) 最小。但题目不允许只选一个数字,那我们考虑选择两个数字(如果最大值固定了多一个数字只会使 \(MEX\) 变大),直接模拟找最大值即可,时间复杂度为 \(O(n)\)。

tip : 其实答案只会在 \(MAX\) 值附近产生,即在含 \(MAX\) 且长度为 \(2\) 的区间中取得。

void solve(){
    int n;
    cin >> n;
    vector<int> a(n);
    int ans = -100;
    for(int i = 0;i < n;i++){
        cin >> a[i];
    }
    for(int i = 0;i < n - 1;i++){
        int mex = 0;
        if((a[i] == 0 || a[i + 1] == 0) && mex == 0){
            mex = 1;
        }
        if((a[i] == 1 || a[i + 1] == 1) && mex == 1){
            mex = 2;
        }
        int tmp = max(a[i], a[i + 1]) - mex;
        ans = max(ans, tmp);
    }
    cout << ans << "\n";
}

D 小苯的平衡序列

​ 排完序后选择两边的数字分别做一次模拟即可,时间复杂度为 \(O(nlogn)\),主要来自排序。

tip : 也可排完序后对每一个数字都枚举删除,然后确定中位数,用前缀和 \(O(1)\) 计算平衡度,求最值,这个时间复杂度为 \(O(nlogn)\)。

void solve(){
    int n;
    cin >> n;
    vector<int> a(n);
    for(int i = 0;i < n;i++){
        cin >> a[i];
    }
    sort(a.begin(), a.end());
    ll ans1 = 0, ans2 = 0;
    ll med1 = a[n / 2], med2 = a[n / 2 - 1];
    for(int i = 0;i < n;i++){
        if(i == 0){
            ans2 += abs(a[i] - med2);
        }else if(i == n - 1){
            ans1 += abs(a[i] - med1);
        }else{
            ans1 += abs(a[i] - med1);
            ans2 += abs(a[i] - med2);
        }
    }
    ll ans = min(ans1, ans2);
    cout << ans << "\n";
}

E 小苯的数字变换

\(\hspace{15pt}\)小苯在研究一种特殊的数字变换。对于一个正整数 \(x (1 \leq x \leq 10^{1000000}\),保证 \(x\) 不含前导 \(0\)),定义一个数字的"根"为不断将其各位数字相加直到得到个位数。例如:
\(\hspace{15pt}\)\(\hspace{15pt}\)- 根 \((38) = 3+8 = 11 → 1+1 = 2\)
\(\hspace{15pt}\)\(\hspace{15pt}\)- 根 \((999) = 9+9+9 = 27 → 2+7 = 9\)

\(\hspace{15pt}\)现在给定一个数字串 \(x\),请你求出:所有 \(x\) 的连续子区间代表的十进制数字(去掉前导 \(0\) 后)的 "根" 之和。

​ 手玩发现:一个数字的 “根” 可以由两两的“根”求“根” / 一个正整数的数根就是其对 \(9\) 取模的值(特殊的,\(9\) 的倍数的根为 \(9\))。

​ 如: \(7963\) 可以先求 \(79\) 的“根”,为 \(7\),然后 \(7\) 与 \(6\) 继续求“根”,然后 \(4\) 与 \(3\) 继续求“根”。

​ 这就可以 \(dp\) 求解,用前面的数递推下一个数字的结果。

void solve(){
    string s;
    cin >> s;
    int n = s.size();
    s = " " + s;
    ll sum = 0;
    vector<vector<int>> dp(n + 1, vector<int>(10, 0));
    for(int i = 1;i <= n;i++){
        dp[i][(s[i] - '0')]++;
        for(int j = 0;j < 10;j++){
            int t = (j + (s[i] - '0'));
            if(t >= 10){
                t = t % 10 + t / 10;
            }
            dp[i][t] += dp[i - 1][j];
        }
        for(int j = 0;j < 10;j++){
            sum += dp[i][j] * j;
        }
    }
    cout << sum << "\n";
}

F 小苯的序列合并

\(\hspace{15pt}\)给定长度为 \(n(1 \leq n \leq 3 \times 10^5)\) 的序列 \(a(0 \leq a_i \leq 10^9)\),你可以对 \(a\) 做如下操作任意次:

$\hspace{23pt}\bullet\ $ 选择一个下标 \(i\ (1 \leqq i < |a|)\),将 \(a_i\) 与 \(a_{i+1}\) 合并起来,结果为 \(a_i\oplus a_{i+1}\)。(其中 \(\oplus\) 表示按位异或运算符,\(|a|\) 表示 \(a\) 当前的长度。)

\(\hspace{15pt}\)所有操作结束后,小苯希望你最大化最终 \(a\) 中所有数字的按位与,即 \(\rm AND(\&)\) 值,请你算一下这个最大值是多少吧。

​ Trick : 这种 \(a_i\) 与 \(a_{i + 1}\) 做运算的式子,如果同时满足交换律和结合律,如 \(\max,\min,\gcd, \text{lcm},+,\times ,\oplus, \&,|\) 等,都可以看作是将 \(a\) 数组进行划分成多块。

​ 结论 : 一定存在一种划分,且划分的块数小于等于 \(2\),使得其结果最大。

​ 证明 : 如果划分的块数 \(\ge 3\),每 \(3\) 个连续的块可继续合并为 \(1\) 块。那原先 \(3\) 个块的与运算结果一定小于或等于异或运算合并后的结果。因为:

  • 当与运算结果的某一位为 \(1\) 时,要求原先的 \(3\) 个块的该位都为 \(1\),此时异或运算和它是等价的。

  • 当只有 \(1\) 个块的某位为 \(1\) 时,异或运算结果的该位仍然为 \(1\),而与运算结果的该位为 \(0\),此时异或运算结果大于与运算结果。

    【三段合并的异或结果是答案的超集,且两段取与操作也一定是答案的超集】

​ 故对于 \(3\) 个及以上的块,使用异或运算合并它们,一定不会更亏。

​ 我们就可以分段枚举分割点,采用前缀异或预处理得到答案,也可用 \(pre\) 和 \(suf\) 数字滚动处理,时间复杂度为 \(O(n)\)。

void solve(){
    int n;
    cin >> n;
    vector<int> a(n);
    int suf = 0, pre = 0;
    for(int i = 0;i < n;i++){
        cin >> a[i];
        suf ^= a[i];
    }
    int ans = suf;
    for(int i = 0;i < n;i++){
        pre ^= a[i];
        suf ^= a[i];
        ans = max(ans, pre & suf);
    }
    cout << ans << "\n";
}

赛时 solve 5 / 6,差一点 pwp,继续加油。

posted on 2025-09-24 16:59  羊毛corn  阅读(19)  评论(0)    收藏  举报

刷新页面返回顶部
 
博客园  ©  2004-2026
浙公网安备 33010602011771号 浙ICP备2021040463号-3