P3718 [AHOI2017初中组]alter

· · 题解

神题,我感觉很阴。

二分答案,答案最小为1,最大为全部长度。如果答案可行则减小上界,否则增大下界。

判断答案是否可行:从左往右更新长度,当我们发现当前长度超过要求长度的时候,应该取反哪一个?

如图,当遍历到第四个F时,允许的最长长度为3。此时长度为4,那么只有两种情况:

但是这样存在一个问题,如果允许的最大长度是1。出现连续一段时(只有两个),除掉两端没有其他点,而反转哪一个都不一定是正确的。上述方法失效了。

对1特判时,这个时候我们直接换一种思路。满足长度为1的序列只有两种:字符N或F开头,并且N和F交替出现。直接检查原字符串能否在k次内转化为两个之一,若可以则答案为1,否则再进行二分答案。

int N, k;
char s[MAX], t[MAX];

bool Judge(int maxlen)
{
    copy(s, s+N, t); //只修改字符串的拷贝
    for (int i=0, tm=0, len=0; i < N; i++) {
        if (i==0 || t[i] != t[i-1]) len = 1;
        else len++;

        if (len > maxlen) {
            if (i != N-1 && t[i]==t[i+1])
                t[i] = (t[i]=='N'?'F':'N');
            //t[i]!=t[i+1]时,修改任意位置都可以,且不影响后面判断,干脆不去真的修改
            if (++tm > k) return 0; //一定会消耗一次次数
        }
    } return 1;
}

bool Judge1() {
    int f[2] = {};
    //将字符串转化为"NFN.."和"FNF.."所需次数
    for (int i = 0; i < N; i++) {
        f[i&1] += s[i]=='N';
        f[(i&1) ^ 1] += s[i]=='F';
    }return f[0]<=k || f[1]<=k;
}

int main() {
    cin >> N >> k >> s;
    if (Judge1()) // 1的情况
        puts("1");
    else { // 其他情况
        int l = 2, r = N;
        while (l < r) {
            int mid = (l + r) >> 1;
            Judge(mid) ? r = mid : l = mid + 1;
        } cout << l;
    }
}