牛客周赛 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]=1ri=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();}
}
感谢大家的点赞和关注,你们的支持是我创作的动力!