zoukankan      html  css  js  c++  java
  • CF660C Hard Process

    原题链接 https://www.luogu.com.cn/problem/CF660C

    题解

    如果直接枚举左右端点,再统计区间内 (0) 的数量是否 (<=k?O(n^3))
    考虑对于区间 ([l,r]) 和区间 ([l,r+1])(0) 的数量差仅取决于 (a[r+1]),所以枚举右端点时 (0) 的数量可以直接转移。(o(n^2))
    因为实际上我们在贪心地选取一个最长的 (0) 的个数不多于 (k) 的区间,所以当右端点取得的 (0) 的个数大于 (k) 的位置时就可以不用继续向右枚举了,而我们右移左端点时也可以用之前统计的 (0) 的数量直接转移,从区间 ([l,r]) 转移到区间 ([l+1,r]) 仅取决于 (a[l])
    这样我们就得到了一个“不停把右端点向右移,(0) 数量 (>k) 时就右移左端点直到 (0) 数量 (<=k)” 的方法,时间复杂度 (O(n))
    这种做法有一堆名字,“(Two-Pointers)”“尺取法”“滑动窗口”(......)
    (Code:)

    #include<iostream>
    #include<cstdio>
    using namespace std;
    const int N=3e5+5;
    int n,m,l,r,L,R,sum,ans;
    int a[N];
    int main()
    {
    	scanf("%d %d",&n,&m);
    	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    	if(m==0)                   //注意特判m=0的情况,此时的问题转化为求最长连续子序列 
    	{
    		int now=0;             //记录目前的连续子序列的长度 
    		for(int i=1;i<=n;i++)
    		{
    			if(a[i]) now++;    
    			else now=0;
    			ans=max(ans,now);  //连续子序列断了,now要清空 
    		}
    		printf("%d
    ",ans);
    		for(int i=1;i<=n;i++) printf("%d ",a[i]);
    		return 0;
    	}
    	l=r=1;sum=a[1]==0;         //sum表示[l,r]内有多少个0 
    	while(l<=r&&r<=n)
    	{
    		if(sum<=m)             //如果[l,r]内0的个数小于sum,那么我们更新一下答案,并继续将右端点右移 
    		{
    			if(r-l+1>ans)
    			{
    				L=l;R=r;       //记录将哪个答案区间进行修改 
    				ans=r-l+1;
    			}
    			r++;
    			sum+=a[r]==0;      //维护新区间的信息 
    		}
    		else                   //否则右移左端点 
    		{
    			sum-=a[l]==0;      //维护新区间的信息 
    			l++;
    		}
    	}
    	printf("%d
    ",ans);
    	for(int i=1;i<=n;i++)
    	{
    		if(L<=i&&i<=R) printf("1 ");  //将更改后的区间0变为1 
    		else printf("%d ",a[i]);  //其余的按原来的输出即可 
    	}
    	return 0;
    }
    

    (Another) (Solution:)
    由上面的贪心思路可知,对于每一个区间左端点 (l),我们只关心区间内 (0) 的个数 (<=k) 的最靠右的右端点 (r),而由于 (r) 越靠右,区间内 (0) 的个数不会减少,所以最优的 (r) 肯定只有 (1) 个,我们可以通过预处理来求:
    (S[i]:) 表示前 (i) 个数中有多少个 (0)(包含 (i)),那么对于区间 ([l,r]) 内的 (0) 的个数,我们可以利用前缀和思想通过 (S[r]-S[l-1])(O(1)) 算出。
    那么如何 (O(1)) 地求出最优的 (r) 呢?
    我们再设 (r[i]) 记录在一些前缀和为 (i) 的数中最大的那个数是多少,注意前缀和是记录的 (0) 的个数。
    这样的话,对于一个 (l),它的最优右端点一定是 (r[S[l-1]+k])
    解释一下:(S[l-1])(l-1) 前面有多少个 (0)(k) 是最优情况下区间 ([l,r])(0) 的数量,相加就是 (r) 前面有多少个 (0),及 (r) 的前缀和我们就可以确定出来了,再通过上面的数组就可以求得这个最优的 (r)
    注意一个细节:当整个序列的 (0) 的数量 (<=k) 时,我们是找不到 (r[S[l-1]+k]) 的,所以如果 (r[i]==0) 我们将其赋值为 (r[i]=n),表示区间的右端点最大是 (n)
    时间复杂度 (O(n))
    (Code:)

    #include<iostream>
    #include<cstdio>
    using namespace std;
    const int N=3e5+5;
    int n,m,L,R,ans;    //[L,R]是最优的修改区间 
    int a[N],S[N],r[N]; //S[i]表示i前面有多少个0,r[i]记录在一些前缀和为i的数中最大的那个数是多少 
    int main()
    {
    	scanf("%d %d",&n,&m);
    	for(int i=1;i<=n;i++) 
    	{
    		scanf("%d",&a[i]);
    		if(!a[i]) S[i]=S[i-1]+1;   //算前缀和 
    		else S[i]=S[i-1];
    		r[S[i]]=max(r[S[i]],i);    //更新一下r数组 
    	}
    	for(int i=1;i<=n;i++)
    	    if(!r[i]) r[i]=n;          //将r[i]=0的部分赋值为r[i]=n 
    	for(int i=1;i<=n;i++)          //枚举左端点 
    	{
    		if(r[S[i-1]+m]-i+1>ans)    //贪心地找出了右端点r[S[i-1]+m] 
    		{
    			L=i;R=r[S[i-1]+m];     //如果更优就更新答案 
    			ans=r[S[i-1]+m]-i+1;
    		}
        }
        printf("%d
    ",ans);
        for(int i=1;i<=n;i++)          
        {
        	if(L<=i&&i<=R) printf("1 ");  //注意将修改区间全部输出1 
        	else printf("%d ",a[i]);   //其余的按原来的输出即可 
    	}
    	return 0;
    }
    
  • 相关阅读:
    SpringBoot:Web开发
    java @Value注解 和 @Data注解
    携程 Apollo 配置中心传统 .NET 项目集成实践
    Spring Boot 整合 JPA 使用多个数据源
    ibatis 核心原理解析!
    Spring Boot 面试,一个问题就干趴下了!
    在Docker中部署Spring Boot项目
    REDIS缓存穿透,缓存击穿,缓存雪崩原因+解决方案
    超全详解Java开发环境搭建
    SpringBoot入门(一):从HelloWorld开始
  • 原文地址:https://www.cnblogs.com/xcg123/p/13424382.html
Copyright © 2011-2022 走看看