CF660C Hard Process

Posted xcg123

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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),那么对于区间 ([l,r]) 内的 (0) 的个数(包含 (i)),我们可以利用前缀和思想通过 (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) 是最优情况下区间内 (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;
}
















以上是关于CF660C Hard Process的主要内容,如果未能解决你的问题,请参考以下文章