[luogu p2882] [USACO07MAR]Face The Right Way G

\(\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}\)

此题要想明白,不拆东墙补西墙便是最优的道理,然后还要想明白区间修改要记得差分优化。

posted @ 2020-10-24 23:47  东北小蟹蟹  阅读(54)  评论(0编辑  收藏