牛客周赛 Round 110

赛时成绩如下:

A. 小苯的数字染色

题目描述

猪猪王图拨号

又来考验他的猪猪部下了,这天他又找到了撤云猪猪

并给了他排成一排的 n 个白色数字。他希望将所有的数字都染红,但他仅允许撤云猪猪每次将相邻的两个白色数字染红,或将相邻的三个白色数字染红。(注意:操作可以进行任意次,但是染红的这 2 或 3 个数字必须都是白色的,而且相邻。)
但撤云猪猪实在太笨了,因此请你来帮帮他。你只需要判断,能否用任意次上述操作将所有的数字染红即可。

解题思路:每次可以选择染红2个/3个相邻的白色数字, n只要大于1都可以实现

#include<bits/stdc++.h>
using namespace std;
void solve(){
    int n;
    cin>>n;
    if(n!=1) cout<<"YES"<<'\n';
    else cout<<"NO"<<'\n';
}
int main(){
    int t=1;
//     cin>>t;
    while(t--){
        solve();
    }
}

B. 小苯的数组重排

题目描述

小苯有一个包含 n 个整数的数组,她想要重新排列数组的元素,使得相邻两个元素的加和之和最大。即最大化:
Sn=(a1​+a2​)+(a2​+a3​)+...+(an−1​+an​)。

你的任务就是帮助小苯找到最大的 S 值。

解题思路:

记sum=a1+a2+...+an

Sn=(a1+a2+...+an-1)+(a2+a3+...+an)=(sum-an)+(sum-a1)=2*sum-(a1+an)

因此要想保证Sn最大就要保证a1+an最小, 因此排列的时候找数组的最小和次小放到数组的开头和结尾即可

#include<bits/stdc++.h>
using namespace std;
using ll =long long;
void solve(){
    int n;
    cin>>n;
    vector<int> a(n);
    ll sum=0;
    for(int i=0;i<n;i++) { cin>>a[i]; sum+=a[i];}
    
    sort(a.begin(),a.end());
    cout<<sum*2-(a[0]+a[1])<<'\n';
}
int main(){
    int t;
    cin>>t;
    while(t--){
        solve();
    }
}

C. 小苯的麦克斯

题目描述

小苯在学习计算机的过程中,接触到了两个概念:MAX 和 MEX,对于一个序列来说,前者表示序列中的最大值,后者表示序列中未出现的最小非负整数。
这天,小红给了小苯一个长度为 n 的序列,她知道小苯分不清这些概念,因此特意提出了一个结合两个概念的题目:
她希望小苯从 a 中选择一个连续区间(不能只选一个数字)l,r (1≤l<r≤n),并最大化区间中所有的数字的 MAX−MEX,即最大值减去最小未出现非负整数的值。
现在请你帮小苯回答一下这个问题吧。

解题思路:从给定的数组中选取子区间,求MAX-MEX的最大值

结合题意观察示例1

1 2 3 4 5

为保证MAX-MEX最大,对于示例1, 这个子区间可以随便选, 只要包含数组中的最大数字即可

eg: [4,5] [3,4,5] [2,3,4,5] [1,2,3,4,5]

因为上面的子数组的mex都等于0

因此只要找一个包含最大数字的子数组即可,数组长度无所谓,选择len=2

子数组没有0,mex就为0,子数组没有1,mex就为1,

遍历的同时维护最大值max-mex即可

#include<bits/stdc++.h>
using namespace std;
void solve(){
    int n;
    cin>>n;
    vector<int> a(n);
    for(int i=0;i<n;i++) cin>>a[i];
    int ans=-1;
    for(int i=0;i+1<n;i++){
        int num_1=a[i],num_2=a[i+1];
        int max_num=max(num_1,num_2),mex=0;
        if(num_1==0||num_2==0){
            mex++;
            if(num_1==1||num_2==1){
                mex++;
            }
        }
        ans=max(ans,max_num-mex);
    }
    cout<<ans<<'\n';
}
int main(){
    int t;
    cin>>t;
    while(t--){
        solve();
    }
}

D. 小苯的平衡序列

题目描述

小苯有一个长度为 n 的整数序列 a1,a2,…,an​。他定义序列的"平衡度"为:
∑i=1n∣ai−median∣
其中 median 表示序列的中位数。如果序列长度是偶数,则中位数定义为较小的那个中间数(即下中位数)。
现在允许从序列中删除恰好一个元素,请计算删除哪个元素可以使剩下的序列的平衡度最小,并输出这个最小的平衡度。

解题思路:

把序列排序为 A[0..n−1],然后考虑删除排序后下标为 k 的元素。剩下的序列仍是有序的,长度为 n−1,它的“下中位数”(题中对偶数长度取较小的中间数)在剩下序列中的下标为m′=[(n-2)/2].floor,将m′映射回原排序数组的下标为

m=m′+(k≤m′?1:0),

因为如果被删元素在原来 m′(或更左)的位置,会把原下标 >k 的元素在剩下序列中的下标左移 1

已知中位数为 A[m],平衡度(删除 k 后)等于对所有 i≠k的 ∣A[i]−A[m]∣ 之和。利用前缀和可以 O(1) 地计算出左侧与右侧的和与个数(需要注意把被删元素从对应区间的和与计数中去掉)。对每个 k 计算候选值并取最小即可

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
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());
    vector<ll> pre_sum(n + 1);
    for (int i = 0; i < n; i++) pre_sum[i + 1] = pre_sum[i] + a[i];
    int m = (n - 2) / 2; ll ans = LLONG_MAX;
    for (int i = 0; i < n; i++) {
        int x = m + (i <= m ? 1 : 0);
        ll l_cnt = x - (i < x ? 1 : 0) , r_cnt = (n - x - 1) - (i > x ? 1 : 0);
        ll sum_l = pre_sum[x] - (i < x ? a[i] : 0);
        ll sum_r = (pre_sum[n] - pre_sum[x + 1]) - (i > x ? a[i] : 0);
        ll v = a[x] * l_cnt - sum_l + sum_r - a[x] * r_cnt;
        ans = min(ans, v);
    }
    cout << ans << '\n';
}
int main() {
    int t; 
    cin >> t;
    while (t--) {
        solve();
    }
}

E. 小苯的数字变换

小苯在研究一种特殊的数字变换。对于一个正整数 x,定义一个数字的"根"为不断将其各位数字相加直到得到个位数。例如:
根 (38)=3+8=11→1+1=2
根 (999)=9+9+9=27→2+7=9
现在给定一个数字串 x,请你求出:所有 x 的连续子区间代表的十进制数字(去掉前导 0 后)的 "根" 之和。

解题思路:

P[i]=(s[0]+s[1]+⋯+s[i−1]) mod 9
举例:s="23"

P[0]=0

P[1]=(2)%9=2

P[2]=(2+3)%9=5

2. 子串模公式
任意子串 s[i..j-1] 的数位和模 9 = (P[j]−P[i]) mod 9
所以只要知道某个右端点 j 的 P[j],就能和所有左端点 i 的 P[i] 配对,得到所有子串 [i..j-1] 的模。

3. cnt[r] 是什么?
它表示:到目前为止,前缀模数等于 r 的前缀有多少个

比如 s="23",我们走到 j=2(cur=5):

之前的前缀有 P[0]=0, P[1]=2。

所以 cnt[0]=1, cnt[2]=1。

它们就是子串的潜在左端点。

4. 为什么要用 cnt?
因为我们要算 (P[j]−P[i]) mod 9

有 cnt[ri] 个前缀模等于 ri。

它们对应的子串模 = (cur - ri) mod 9。

所以一次性加 cnt[ri] 到 f[(cur-ri)%9]。

5. f 的作用
f[r] = 所有子串里,数位和模 9 等于 r 的子串个数。

r=1..8:数字根就是 r。

r=0:要拆分成“全零子串”和“非零但 %9==0 的子串”。

eg:
s = "23"

初始化:cnt[0]=1,f 全 0。

j=1,cur=P[1]=2
ri=0 → res=(2-0)%9=2 → f[2]+=cnt[0]=1

更新 cnt[2]++

现在 f[2]=1(子串 "2")。

j=2,cur=P[2]=5
ri=0 → res=(5-0)%9=5 → f[5]+=cnt[0]=1

ri=2 → res=(5-2)%9=3 → f[3]+=cnt[2]=1

更新 cnt[5]++

最后:

f[2]=1 ("2")

f[3]=1 ("23")

f[5]=1 ("3")

 

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void solve(){
    string a; 
    cin >> a;
    int n = a.size();
    ll c = 0;
    for(int i=0;i<n;){
        if(a[i] != '0'){ i++; continue; }
        int j = i;
        while(j < n && a[j] == '0') j++;
        ll len = j - i;
        c += len * (len + 1) / 2;
        i = j;
    }
    vector<int> d(n+1, 0);
    for(int i=0;i<n;i++){
        int x = a[i]-'0';
        d[i+1] = (d[i] + x) % 9;
    }
    ll e[9] = {0}; ll f[9] = {0};
    e[d[0]] = 1;
    for(int i=1;i<=n;i++){
        int cur = d[i];
        for(int r=0;r<9;r++){
            int res = (cur - r) % 9;
            if(res < 0) res += 9;
            f[res] += e[r];
        }
        e[cur] += 1;
    }
    ll ans = 0;
    for(int r=1;r<=8;r++) ans += r * f[r];
    ll t = f[0];
    ll nz = t - c;
    if(nz > 0) ans+=9*nz;
    cout << ans << '\n';
}
int main(){
    int t;
    cin >> t;
    while(t--){
        solve();
    }
    return 0;
}

F. 小苯的序列合并

题目描述

给定长度为 nnn 的序列 a,你可以对 a 做如下操作任意次:
选择一个下标 i (1≦i<∣a∣),将 ai与 ai+1​ 合并起来,结果为 ai⊕ai+1(其中⊕ 表示按位异或运算符,∣a∣表示 a 当前的长度。)
所有操作结束后,小苯希望你最大化最终 a 中所有数字的按位与,即 AND值,请你算一下这个最大值是多少吧。

解题思路:

任意次操作ai和ai+1合并,最后得到的数组a再进行合并,输出合并最大值

合并等价与划分

结论题:一定可以划分成不超过两段区间

1. 直接整个区间进行 &

2. 每个区间的中间节点 ak

(a1 xor a2 xor...xor ak) & (ak+1 xor ... xor an)

然后前缀异或和即可

证明:

偶数最终变成2,奇数最终变成1

#include<bits/stdc++.h>
using namespace std;
void solve(){
    int n; cin >> n;
    vector<int> a(n); for(int i=0;i<n;i++) cin>>a[i];
    int ans = 0; 
    for (auto& x : a) ans ^= x; 

    for (int i = 1; i < n; i++) { 
        a[i] ^= a[i - 1]; 
    }
    for (int i = 0; i < n; i++) ans = max(ans, (a[n - 1] ^ a[i]) & a[i]);
    cout << ans << '\n'; 
}
int main(){
    int t;
    cin>>t;
    while(t--){
        solve();
    }
}

感谢大家的点赞和关注,你们的支持是我创作的动力!

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值