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的主要内容,如果未能解决你的问题,请参考以下文章

题解CF1165F2 Microtransactions (hard version)

CF367C. Hard problem

CF802LSend the Fool Further! (hard) 高斯消元

cf1216E2 Numerical Sequence (hard version) 二分查找思维题

题解 CF61B Hard Work

CF1486 C2. Guessing the Greatest (hard version)