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)
CF802LSend the Fool Further! (hard) 高斯消元