1 条题解
-
0
核心思路:
1. 判断是否已经有序
代码首先通过一个循环
if(ss[i]>ss[i+1])遍历字符串,检查是否存在"10"这种逆序对。- 如果
kk == 0,说明整个字符串已经是非递减的(即形如000...111),无需任何操作,直接输出0。
2. 字符串下标处理
为了方便前缀和的计算,字符串被改造成了
ss = " " + ss,使其下标从 1 开始到 n 结束 。
特殊情况:首字符为
'1'(ss[1]=='1')为什么特殊?
如果字符串的第一个字符是
1,那么整个字符串前缀中就不存在0。这意味着我们无法使用 AND 操作把前面的1变成0(因为没有0作为前缀)。解决思路(全变1)
既然没法变
0,我们唯一的出路就是把后面所有的0全都变成1(通过 OR 操作)。由于第一个字符已经是1,后面所有的0都可以通过 OR 操作一次性变成1。代码实现
代码中
d统计了字符串中0的总个数。因此直接输出num1 = d,代表把所有的0变成1需要的操作次数。
一般情况:首字符为
'0'(else 分支)这是代码的核心部分,使用了前缀和 + 枚举分界线的方法。
清空与初始化
循环
for(int i=0;i<=n;i++) p[i]=0, q[i]=0;用于重置前缀和数组。构建前缀和数组
p[i]记录的是从下标 1 到 i 中,字符'1'的个数。q[i]记录的是从下标 1 到 i 中,字符'0'的个数。
因为首字符是
'0',所以我们有底气使用 AND 操作把前面的1变成0。枚举分界线(寻找最优解)
最终排序好的字符串,一定可以看作是在某个位置
i切了一刀:i的左边全变成0,i的右边全变成1。代码通过
for(int i=1;i<=n;i++)枚举这条分界线。- 左边需要的操作次数:把左边所有的
1变成0,需要p[i]次操作。 - 右边需要的操作次数:把右边所有的
0变成1,需要总0的个数减去左边0的个数,即q[n] - q[i]。 - 当前分界线总代价:
p[i] + q[n] - q[i]。
更新最小值
变量
mx初始设为极大值1e9,在循环中不断取min(mx, p[i] + q[n] - q[i]),找到所有分割点中代价最小的一个。最后输出mx。代码:
#include <stdio.h> #include <string.h> #define MAXN 200209 long long p[MAXN]; long long q[MAXN]; char ss[MAXN]; int main() { int t; scanf("%d", &t); while (t--) { int n; scanf("%d", &n); int kk = 0; scanf("%s", ss + 1); /* 下标从 1 开始读入 */ for (int i = 1; i <= n - 1; i++) { if (ss[i] > ss[i + 1]) { kk = 1; break; } } if (kk == 0) { printf("0\n"); } else { long long num1 = 1000000000LL; if (ss[1] == '1') { long long d = 0; /* 全变 1 的代价:0 的个数 */ for (int i = 1; i <= n; i++) { if (ss[i] == '0') { d++; } } num1 = d; printf("%lld\n", num1); } else { for (int i = 0; i <= n; i++) { p[i] = 0; q[i] = 0; } for (int i = 1; i <= n; i++) { if (ss[i] == '1') { p[i] = p[i - 1] + 1; q[i] = q[i - 1]; } else { q[i] = q[i - 1] + 1; p[i] = p[i - 1]; } } long long mx = 1000000000LL; for (int i = 1; i <= n; i++) { long long cur = p[i] + q[n] - q[i]; if (cur < mx) mx = cur; } printf("%lld\n", mx); } } } return 0; } - 如果
- 1
信息
- ID
- 1249
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- (无)
- 递交数
- 11
- 已通过
- 2
- 上传者