(mathtt{Link})
(mathtt{Summarization})
给定一个 01 序列,定义 (m_k) 为使得这个 01 序列中的0全部变成1的最少的操作次数。其中一次操作定义为:对长度为 (k) 的子序列中的每一个数 (operatorname{xor} 1)。求 (min_{i=1}^nm_i) 和此时的 (i)。
(mathtt{Solution})
首先枚举 (l) 作为区间长度。
问题就转化为了,给定 (l),使得长度为 (l) 的区间进行批量取反,需要至少多少次操作把整个串变成01.
解决思路是,从前到后枚举这个串,只要碰到 (0),就以这个 (0) 为左端点,进行区间取反。然后继续枚举,碰到 (0) 就区间取反。如果发现这个 (0) 到串尾的长度不够 (l),说明在此 (l) 下,无解。
为什么这样做是对的?因为我们是从前到后处理的,因此处理到某个字符的时候,前面的一定都变成 (1) 了。此时如果你不以左端点,而是将前面已经变成 (1) 的给取反了,那么就将是拆东墙补西墙,会导致答案增多。因此,一直以左端点取反,得到的结果一定最优。
想明白这点再往下看哦。
想到这里,时间复杂度是 (mathcal{O}(n^3)),因为一层枚举长度 (l),一层枚举这个串,一层修改操作,层层都是 (n) 级别。
枚举长度 (l) 和枚举这个串是没法优化了,可以证明如果不枚举一定会有遗漏的死角。但是修改操作,我们可以用差分优化成 (mathcal{O}(1))。
那么最后时间复杂度就可以优化为 (mathcal{O}(n^2))。(mathcal{O}( ext{可过}))!
(mathtt{Code})
/*
* @Author: crab-in-the-northeast
* @Date: 2020-10-24 12:29:41
* @Last Modified by: crab-in-the-northeast
* @Last Modified time: 2020-10-24 12:40:16
*/
#include <iostream>
#include <cstdio>
#include <climits>
#include <cstring>
const int maxn = 10005;
bool a[maxn], sum[maxn];
int main() {
int n;
std :: scanf("%d", &n);
for (int i = 1; i <= n; ++i) {
char dir;
std :: cin >> dir;
a[i] = (dir == 'F');
}
int m = INT_MAX / 4, k;
for (int l = 1; l <= n; ++l) {
std :: memset(sum, false, sizeof(sum));
bool b = false;
int cnt = 0;
for (int i = 1; i <= n; ++i) {
b ^= sum[i];
if ((!a[i] ^ b)) {
if (i + l - 1 > n) {
cnt = INT_MAX / 4;
break;
}
b ^= 1;
sum[i + l] ^= 1;
++cnt;
}
}
if (cnt < m) {
m = cnt;
k = l;
}
}
std :: printf("%d %d
", k, m);
return 0;
}
(mathtt{More})
此题要想明白,不拆东墙补西墙便是最优的道理,然后还要想明白区间修改要记得差分优化。