P7635 [COCI2010-2011#5] DVONIZ
算法分析:尺取
看到本题,有些同学可能会想到二分。但实际上,答案并不满足单调性。为了叙述方便,以下称区间 \(i \to i+k-1\) 为左区间,区间 \(i+k \to i+2\times k-1\) 为右区间。在 \(k\) 增大时,左区间的值的确满足单调性,但右区间的值有减有增,不满足单调性。具体看以下样例:
6 10
1 6 3 8 1 1
当 \(i=1 ,k=2\),时,右区间和大于 \(s\),不满足条件,但当 \(k\) 取 \(k=3\) 时,满足条件。
由此可见,答案并不满足单调性。
根据题意从左往右依次计算的时间复杂度为 \(\mathcal{O}(n^2)\) ,显然不能满足 \(N\le 10^5\) 的要求。于是我们考虑反过来,从右往左计算。每次计算 \(k\) 值时,可以利用上一次计算的结果进行调整,从而在较小时间内得到结果。记 \(r\) 为左区间的右端点,可以证明,在由 \(i\to i-1\) 的过程中,决策区间 \(i\to r\) 的右端点 \(r\) 是单调不增的。我们将该性质作为循环不变式证明算法的正确性:
在第 \(i\) 次循环开始前,\(i+1 \to r\) 算得的 \(r\) 是当前可能的最大右边界。即,在整个过程中,\(r\) 是单调不增的。
初始化:
在 \(i=n\) 时,显然 \(k=0\) 是当前的最优解。
保持:
在 \(i+1\to i\) 时,存在两种情况。
- \(i\to r\) 仍符合条件,此时满足 \(sum_r-sum_{l-1}\le s\) 且 \(sum_{r+k}-sum_r\le s\)。若在情况 \(i+1\) 时,\(r\) 受到 \(r+k\le n\) 的限制而不能增大,那么在当前状态下,\(r\) 同样受此限制。若 \(r+k<n\),假设 \(r\) 能增大,那么 \(k\) 也随之增大,于是左区间的和在前一次的基础上继续增大。在 \(r+k<n\) 的情况下,根据循环不变式,\(k\) 必定已经取到极限值,即左区间不能继续向右扩张。这与 \(k\) 增大矛盾,因此 \(r\) 同样不会增大,循环不变式得到保持。
- \(i\to r\) 不符合条件。那么我们将 \(r\) 左移,左、右区间均随之减小,且必定一个存在 \(r\) 使得 \(sum_r-sum_{l-1}\le s\) 且 \(sum_{r+k}-sum_r\le s\)。且此时左区间恰满足条件,此时满足循环不变式。
终止:
显然,$i \ge 1 $ ,在 \(i=1\) 之后,过程终止。
综上所述,循环不变式得到证明,算法正确性得到保证。同时,在算法中, \(r\) 随 \(i\) 的变化而单调不增,因此时间复杂度为 \(\mathcal{O}(n)\)。
code:
#include<bits/stdc++.h>
#define reg register
#define ll long long
#define F(i,a,b) for(reg int i=a;i<=b;++i)
using namespace std;
inline int read();
const int N=1e5+1;
int n,s,sum[N],ans[N];
int main() {
n=read(),s=read();
F(i,1,n)sum[i]=sum[i-1]+read();//前缀和
reg int r=n,k;
for(reg int i=n; i; --i) {//从右往左计算
while(r>=i) {
k=r-i+1;//r为左区间的右端点。
if(r+k<=n and sum[r]-sum[i-1]<=s and sum[r+k]-sum[r]<=s)break;
--r;
}
ans[i]=r-i+1<<1;//计算出可行的长度。
}
F(i,1,n)printf("%d\n",ans[i]);
return 0;
}
inline int read() {
reg int x=0;
reg char c=getchar();
while(!isdigit(c))c=getchar();
while(isdigit(c))x=(x<<1)+(x<<3)+(c^48),c=getchar();
return x;
}
欢迎交流讨论,请点个赞哦~
本文来自博客园,作者:Maplisky,转载请注明原文链接:https://www.cnblogs.com/lbh2021/p/14940452.html

浙公网安备 33010602011771号