\(\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}(\text{可过})\)!
\(\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\n", k, m);
return 0;
}
\(\mathtt{More}\)
此题要想明白,不拆东墙补西墙便是最优的道理,然后还要想明白区间修改要记得差分优化。