zoukankan      html  css  js  c++  java
  • [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}( 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})

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

  • 相关阅读:
    斐波那契数列的量化分析
    GridView编辑删除操作
    Linux crontab 命令格式与具体样例
    VB.NET版机房收费系统---组合查询
    XMLHTTP使用具体解释
    Android 在子线程中更新UI的几种方法
    国产操作系统剽窃Linux内核可耻!
    Android的PVPlayer介绍
    稀疏矩阵
    很好的理解遗传算法的样例
  • 原文地址:https://www.cnblogs.com/crab-in-the-northeast/p/luogu-p2882.html
Copyright © 2011-2022 走看看