zoukankan      html  css  js  c++  java
  • USACO07MAR Face The Right Way G 差分

    题目链接 https://www.luogu.com.cn/problem/P2882

    分析

    这个题来看的话好像有点难下手,不如再去读一遍题 N遍,发现一句话很重要Each time the machine is used, it reverses the facing direction of a contiguous group of K cows in the line,就是说只能翻转固定的长度区间,那这样是不是就可以枚举区间了?枚举一层区间,再枚举每次起点,最后加上区间修改,时间复杂度(O(N^3)),肯定会T掉,接下来就考虑优化了。
    优化怎么入手呢?时间主要就是出在这三层循环上,只要省掉一层循环,时间复杂度就能到(O(N^2)),这样就可以过,第一层循环,显然不能省略,第二层同样,只有在区间修改这一层循环上可以做点手脚,回忆区间修改,有几种做法,线段树,树状数组,还有差分,前两者用在这都有点大材小用或是说不是很合适,因为判断是否区间修改完成不好判断,而差分用在这个区间上就很合适了。那我们大概思路也就有了,首先读入数组,将B标记成1,F标记成0,这里怎么标记都无所谓,然后利用枚举区间,差分修改,最后输出答案,下面考虑一下细节。
    我们枚举区间完,要从左到右一次反转区间,为什么呢?题目中要求的是最小次数,就是要先保证次数最小,再考虑区间长度,而我们如果先修改后面的,把后面改好了,再去改前边的,结果一定不会比先改前边的好(有可能相等,如00100),所以我们为保证最小次数,一定从最左端开始依次枚举,如果这个点不符合,就把他后面的整个区间翻转,这里就要用到差分了,肯定直接修改会T掉,我们可以考虑,如果这个区间要修改,那么原来的1会变成2,0会变成1,好像没什么规律,但再看就发现所有的奇数都需要改变,偶数就不用,每次修改给整个区间加一,判断奇偶数就行,然后这就变成了一个区间加一个数的操作,相信大家应该都会。这样修改就完成了,那么怎么判断能不能完成题目的任务呢?由题意可以知道如果当前区间长度小于修改的区间长度,是不能修改的,也就是从n往前的长度为len的区间总是无法被修改的,所以判断这一段区间内有无不满足条件的点即可。
    最后找答案的时候也有一个地方,就是当操作修改次数不同时,直接用操作修改次数最小的那个答案就行,但如果当前操作次数和原来答案相同,是不是要考虑一下区间长度改成最小值?答案显然是不是,…………,因为我们是从小到大枚举的区间长度,所以在遇到相等的时候,已经得到的答案的区间长一定是小的,所以只在次数不同时修改答案,但判断上也不会错。

    其他优化

    当然以下优化不加也没问题,毕竟算法时间复杂度足够过掉这道题。
    我做完之后看了看时间大概700ms左右,好像有点高,看别人的时间好像没有特别大,所以我加了加小优化。

    为方便说,由上到下一次标号(1-4),1,2跑的时间还是挺快的但没啥用,(NOIp)不可能给你开O2也不可能给你c++17,所以还是看一下3和4,这俩时间大概有一倍的关系,看一下代码吧

    3

    #include<cstdio>
    #include<cstring>
    using namespace std;
    const int N=5e3+10;
    char s[3];
    int cf[N],a[N];
    int min(int a,int b){
        if(a<b)return a;
        else return b;
    }
    int main(){
        int n;scanf("%d",&n);
        for(int i=1;i<=n;i++){
            scanf("%s",s);
            if(s[0]=='B')a[i]=1;
            else a[i]=0;
        }
        int res=0x3f3f3f3f,ans=0x3f3f3f3f;
        for(int len=1;len<=n;len++){
            int cnt=1,k=0;memset(cf,0,sizeof(cf));
            for(int i=1;i<=n;i++){
                cf[i]+=cf[i-1];
                if(i+len-1<=n){
                    if(a[i]+cf[i]&1){
                        cf[i]++;cf[i+len]--;k++;
                    }
                }else if(cf[i]+a[i]&1){cnt=0;break;}
            }
            if(cnt)
                if(k<ans){
                    ans=k;res=len;
                }
                else if(k==ans)res=min(res,len);
        }
        printf("%d %d",res,ans);
        return 0;
    }
    
    

    4

    #include<cstdio>
    #include<cstring>
    using namespace std;
    const int N=5e3+10;
    char s[3];
    int cf[N],a[N];
    int min(int a,int b){
        if(a<b)return a;
        else return b;
    }
    int main(){
        int n;scanf("%d",&n);
        for(int i=1;i<=n;i++){
            scanf("%s",s);
            if(s[0]=='B')a[i]=1;
            else a[i]=0;
        }
        int res=0x3f3f3f3f,ans=0x3f3f3f3f;
        for(int len=1;len<=n;len++){
            int cnt=1,k=0;memset(cf,0,sizeof(cf));
            for(int i=1;i<=n;i++){
                cf[i]+=cf[i-1];
                if(i+len-1<=n){
                    if(a[i]+cf[i]&1){
                        cf[i]++;cf[i+len]--;k++;
                    }
                }else if(cf[i]+a[i]&1)cnt=0;
            }
            if(cnt)
                if(k<ans){
                    ans=k;res=len;
                }
                else if(k==ans)res=min(res,len);
        }
        printf("%d %d",res,ans);
        return 0;
    }
    
    

    其实就是少一个break,感觉这个加上还是很有必要的,因为可能极限数据的时候,CCF那评测机状态不好,再卡一下,可能会出问题。

    问题

    那么有没有可能最开始全部是朝前的呢?答案是没有,英文题面中已经讲到,有一些牛,所以说不可能其实全部朝前边的。

  • 相关阅读:
    python打包生成可执行文件教程
    MATLAB中冒号的用法解析
    C#中StreamWriter类使用总结
    C#中StreamReader类读取文件使用示例
    C#中WinFrom保存文件SaveFileDialog类的使用方法
    C#中FolderBrowserDialog类打开文件夹使用说明
    C#中的WinFrom技术实现串口通讯助手(附源码)
    评价指标的计算:accuracy、precision、recall、F1-score等
    【转】一张图解析FastAdmin中的表格列表的功能
    哈工大LTP语言分析:分词、词性标注、句法分析等
  • 原文地址:https://www.cnblogs.com/anyixing-fly/p/12640589.html
Copyright © 2011-2022 走看看