洛谷题单指南-前缀和差分与离散化-P2882 [USACO07MAR] Face The Right Way G
原题链接:https://www.luogu.com.cn/problem/P2882
题意解读:一个有F、B组成的序列,每次可以翻转k个连续子序列,翻转:F->B,B->F,计算最少翻转多少次可以将序列都变成F,并求相应的k。
解题思路:
为方便处理,设F为1,B为0
1、朴素做法
枚举k:1~n
枚举序列,一旦遇到0,就将连续k个字符翻转,如果可翻转的字符不足k个,则结束,这里也是贪心的思想,既然要全部变成F,当然遇到B就开始翻转会比较合理,这样已经处理过的位置就都变成F了,不用回头去处理。
时间复杂度:枚举k是n,枚举序列是n,将k个字符翻转是n,一共n^3,需要考虑优化
80分代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 5005;
int a[N], b[N];
int n, ansk, ansm = INT_MAX;
int main()
{
cin >> n;
char c;
for(int i = 1; i <= n; i++)
{
cin >> c;
if(c == 'F') a[i] = 1;
else if(c == 'B') a[i] = 0;
}
for(int k = 1; k <= n; k++)
{
memcpy(b, a, sizeof(a)); //把a拷贝一份
int m = 0;
for(int i = 1; i <= n; i++)
{
if(b[i] == 0)
{
int end = i + k - 1;
if(end > n) break;
//翻转操作
for(int j = i; j <= end; j++)
{
b[j] ^= 1;
}
m++;
}
//如果i走到最后,且最后一个已经变为F
if(i == n && b[i] == 1)
{
if(ansm > m)
{
ansm = m;
ansk = k;
}
}
}
}
cout << ansk << " " << ansm;
return 0;
}
2、差分优化
TLE的关键在于翻转操作需要枚举k个元素,一个一个处理,但实际上,并不需要真的做翻转,只需要做一个标记:
bool flag=true表示从当前的i位置往后执行翻转,f[i + k]=true表示从i + k的位置执行翻转,因为做两次翻转等于不变,因此这样的效果就是标记了
从i到i-k+1范围执行翻转。
这样一来,在判断a[i]的数值时,可以结合flag来计算翻转之后的值。
要注意flag和f[]的几个更新点:
当要翻转时,flag = flag ^ 1,f[i + k] ^= 1
当遇到f[i]=true时,flag = flag ^ 1
100分代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 5005;
int a[N], b[N];
int n, ansk, ansm = INT_MAX;
int main()
{
cin >> n;
char c;
for(int i = 1; i <= n; i++)
{
cin >> c;
if(c == 'F') a[i] = 1;
else if(c == 'B') a[i] = 0;
}
for(int k = 1; k <= n; k++)
{
memcpy(b, a, sizeof(a)); //把a拷贝一份
int m = 0;
bool flag = false; //当前是否翻转
bool f[N] = {false}; //f[i]=true表示从i之后是否翻转
for(int i = 1; i <= n; i++)
{
if(f[i] == true) flag ^= 1; //当前状态改变
if((b[i] ^ flag) == 0)
{
if(i + k - 1 > n) break;
//翻转操作
flag ^= 1; //当前状态基础上再翻转
f[i + k] ^= 1; //i+k之后翻转
m++;
}
//如果i走到最后,且最后一个已经变为F
if(i == n && (b[i] ^ flag) == 1)
{
if(ansm > m)
{
ansm = m;
ansk = k;
}
}
}
}
cout << ansk << " " << ansm;
return 0;
}