牛客周赛 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,继续加油。
浙公网安备 33010602011771号