A小柒与啦啦啦的博弈
链接:https://ac.nowcoder.com/acm/contest/111921/A
由于轮流选取宝物,每次选取都争取最大利益化。小柒先手,难道是贪心算法? 简单来说就是选取剩下宝物中价值最大的宝物。直接将宝物价值按升序排列,再由两人按顺序累加即可。
主要是数据会爆 int ,选择 long long 即可。
Code
#include<stdio.h>
int com(const void *a, const void *b) {
return *(int*)b - *(int*)a;
}
int main() {
int n;
scanf("%d", &n);
int* num = malloc(sizeof(int) * n);
for(int i = 0; i < n; i++) {
scanf("%d", &num[i]);
}
qsort(num, n, sizeof(int), com);
long long ans1 = 0, ans2 = 0;
for(int i = 0; i < n; i++) {
if(i & 1) ans2 += num[i];
else ans1 += num[i];
}
free(num);
printf("%lld %lld", ans1, ans2);
}
时间复杂度:O(nlog(n)),主要来自快排
空间复杂度:O(n),一个num
数组
B小柒的交替子数组
链接:https://ac.nowcoder.com/acm/contest/111921/B
解法一:
由于奇偶交替数组可以分为两种
- 开头为奇数
- 开头为偶数
很容易想到动态规划,从下标 0 到下标 i 所要操作的次数。可以用两个 dp 数组来完成,数组 dp0 表示开头为偶数情况,数组 dp1 表示开头为奇数情况。
- dp0:开头为偶数,即偶数下标为偶数,奇数下标为奇数
- dp1:开头为奇数,即偶数下标为奇数,奇数下标为偶数
在此之前先对原数组进行预处理:即奇数化一 偶数化零
int* num = malloc(sizeof(int) * n);
for (int i = 0; i < n; i++) {
int temp;
scanf("%d", &temp);
num[i] = (temp & 1);
}
状态转移方程也很好写:
dp0[0] = (num[0] != 0); // 初始化dp0
dp1[0] = (num[0] != 1); // 初始化dp1
for (int i = 1; i < n; i++) {
dp0[i] = dp0[i-1] + (num[i] != (i&1 ? 1 : 0));
// 当前状态 = 前一个状态 + 当前数字在 dp0 情况下是否需要操作(是即为1,否即为0)
dp1[i] = dp1[i-1] + (num[i] != (i&1 ? 0 : 1));
// 当前状态 = 前一个状态 + 当前数字在 dp1 情况下是否需要操作(是即为1,否即为0)
}
// 其实就是看当前数字(0 或 1)与理想状态下数字(0 或 1)是否相同,相同则条件表达式为错,返回0;不同则条件表达式正确,返回1;
要知道我们现在推出的dp0 dp1
数组的含义是从下标零到下标i所要操作的次数,但题目是要求在长度为n的数组中截取连续长度为m的数组所需最少操作次数。没错,就是前缀和
for (int i = m-1; i < n; i++) {
int start = i - m + 1;
int cost0 = dp0[i] - (start > 0 ? dp0[start-1] : 0);
int cost1 = dp1[i] - (start > 0 ? dp1[start-1] : 0);
ans = fmin(ans, fmin(cost0, cost1)); // 每次取最小值
}
Code
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<math.h>
int main() {
int n, m;
scanf("%d %d", &n, &m);
int* num = malloc(sizeof(int) * n);
for (int i = 0; i < n; i++) {
int temp;
scanf("%d", &temp);
num[i] = (temp & 1);
}
int ans = n;
int* dp0 = malloc(sizeof(int) * n);
int* dp1 = malloc(sizeof(int) * n);
dp0[0] = (num[0] != 0);
dp1[0] = (num[0] != 1);
for (int i = 1; i < n; i++) {
dp0[i] = dp0[i-1] + (num[i] != (i&1 ? 1 : 0));
dp1[i] = dp1[i-1] + (num[i] != (i&1 ? 0 : 1));
}
for (int i = m-1; i < n; i++) {
int start = i - m + 1;
int cost0 = dp0[i] - (start > 0 ? dp0[start-1] : 0);
int cost1 = dp1[i] - (start > 0 ? dp1[start-1] : 0);
ans = fmin(ans, fmin(cost0, cost1));
}
free(num);
free(dp0);
free(dp1);
printf("%d\n", ans);
return 0;
}
时间复杂度:O(n)
空间复杂度:O(n)
解法二:
动态规划 + 前缀和 固然很优秀,但为什么不直接一步到位滑动窗口
解决呢
Code
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<math.h>
int main() {
int N, M;
scanf("%d %d", &N, &M);
int *v = (int *)malloc(N * sizeof(int));
for(int i = 0; i < N; i++) {
int temp;
scanf("%d", &temp);
v[i] = temp & 1;
}
// 同样的预处理
int ans = N, cur = 0;
// 这里直接以开头为偶数为理想状态情况(其实随便取都一样)
for(int i = 0; i < N; i++) {
// 计算当前位置元素奇偶性与理想奇偶性(基于位置 i)的差异
cur += v[i] ^ (i & 1);
// 当窗口长度超过 M 时,移除窗口左端元素的影响
if (i >= M) {
cur -= v[i - M] ^ ((i - M) & 1);
}
// 当窗口长度达到 M - 1 及以上时,计算并更新最小操作次数
if (i >= M - 1) {
// 取当前差异值和其补集(M - cur)中的较小值,再更新全局最小值
ans = fmin(ans, fmin(cur, M - cur));
}
}
printf("%d\n", ans);
free(v);
return 0;
}
时间复杂度:O(n)
空间复杂度:O(n)
C小柒的幸运数
链接:https://ac.nowcoder.com/acm/contest/111921/C
一开始想法是二维动态规划,嗯,好吧,还是官解厉害,采用滑动窗口
结合字符频次统计
的方法,动态维护窗口内的逆序对数量。
核心思想
- 滑动窗口:用双指针
left
和right
表示窗口的左右边界,逐步扩展右边界,并在逆序对数量超过 x 时收缩左边界。 - 逆序对计算:利用数组
phala
统计窗口中各字符的出现次数- 新增字符时,累加比它大的字符数量。
- 移除字符时,减少比它小的字符数量。
Code
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
int main() {
char s[200005];
long long x;
scanf("%s %lld", s, &x);
int len = strlen(s);
long long phala[26];
int left = 0, right = 0;
long long val = 0, now = 0;
for(;right < len; right++) {
int current_char = s[right] - 'a';
for(int i = current_char + 1; i < 26; i++) {
now += phala[i];
}
phala[current_char]++;
// 时刻更新 val
if(llabs(val - x) > llabs(now - x)) {
val = now;
}
if(val == x) break; // 提前结束,小优化
// 这里应是 while, 而非 if, 维持边界要一步到位!
while(left <= right && now > x) {
int left_char = s[left] - 'a';
for(int i = 0; i < left_char; i++) {
now -= phala[i];
}
phala[left_char]--;
left++;
// 时刻更新 val
if(llabs(val - x) > llabs(now - x)) {
val = now;
}
}
}
printf("%lld\n", llabs(val - x)); // 注意最后输出的是差的绝对值
return 0;
}
时间复杂度:O(26n), 每个字符最多被加入和移除窗口各一次,每次操作需遍历 26 个字母。
空间复杂度:O(26), 即为 phala 数组