NOIP复赛复习14尺取法与折半枚举Word文件下载.docx
《NOIP复赛复习14尺取法与折半枚举Word文件下载.docx》由会员分享,可在线阅读,更多相关《NOIP复赛复习14尺取法与折半枚举Word文件下载.docx(12页珍藏版)》请在冰豆网上搜索。
S
所以从as+1开始总和最初超过S的连续子序列如果是as+1+...+at′−1的话,则必然有t≤t′。
用下面的图来解释比较清晰:
#include<
iostream>
cstdio>
cmath>
algorithm>
#definesfscanf
#definepfprintf
usingnamespacestd;
constintMaxn=100010;
intT,n,s;
intsum[Maxn];
intmain()
{
inta;
sf("
%d"
&
T);
while(T--)
{
inttail=-1,head=-1;
%d%d"
n,&
s);
for(inti=0;
i<
n;
i++)
a);
if(i==0)sum[i]=a;
elsesum[i]=sum[i-1]+a;
if(tail==-1andsum[i]>
=s)
tail=i;
}
if(tail==-1)
pf("
0\n"
);
continue;
intMin=n;
while(head<
tail)
if(sum[tail]-sum[head+1]>
=s)
head++;
Min=min(Min,tail-head);
elseif(tail<
n-1)tail++;
else
break;
%d\n"
Min);
return0;
}
POJ3320
题意:
一本书有P页,每一页都一个知识点,求去最少的连续页数覆盖所有的知识点。
分析:
和上面的题一样的思路,如果一个区间的子区间满足条件,那么在区间推进到该处时,右端点会固定,左端点会向右移动到其子区间,且其子区间会是更短的,只是需要存储所选取的区间的知识点的数量,那么使用map进行映射以快速判断是否所选取的页数是否覆盖了所有的知识点。
cstring>
set>
map>
#defineMAX1000010
#defineLLlonglong
#defineINF0x3f3f3f3f
inta[MAX];
map<
int,int>
cnt;
set<
int>
t;
intp,ans=INF,st,en,sum;
intmain()
{
scanf("
&
p);
for(inti=0;
i<
p;
i++)scanf("
a+i),t.insert(a[i]);
intnum=t.size();
while
(1){
while(en<
p&
&
sum<
num)
if(cnt[a[en++]]++==0)sum++;
if(sum<
num)break;
ans=min(ans,en-st);
if(--cnt[a[st++]]==0)sum--;
}
printf("
ans);
}
POJ2566
给定一个数组和一个值t,求一个子区间使得其和的绝对值与t的差值最小,如果存在多个,任意解都可行。
明显,借用第一题的思路,既然要找到一个子区间使得和最接近t的话,那么不断地找比当前区间的和更大的区间,如果区间和已经大于等于t了,那么不需要在去找更大的区间了,因为其和与t的差值更大,然后区间左端点向右移动推进即可。
所以,首先根据计算出所有的区间和,排序之后按照上面的思路求解即可。
#defineMAX100010
typedefpair<
LL,int>
LLa[MAX],t,ans,tmp,b;
intn,k,l,u,st,en;
psum[MAX];
LLmyabs(LLx)
returnx>
=0?
x:
-x;
while(scanf("
%d%d"
k),n+k){
sum[0]=p(0,0);
for(inti=1;
=n;
i++){
%I64d"
a+i);
sum[i]=p(sum[i-1].first+a[i],i);
sort(sum,sum+1+n);
while(k--){
t);
tmp=INF;
st=0,en=1;
while(en<
=n){
b=sum[en].first-sum[st].first;
if(myabs(t-b)<
tmp){
tmp=myabs(t-b);
ans=b;
l=sum[st].second;
u=sum[en].second;
if(b>
t)st++;
elseif(b<
t)en++;
elsebreak;
if(st==en)en++;
if(u<
l)swap(u,l);
%I64d%d%d\n"
ans,l+1,u);
总结:
尺取法的模型便是这样:
根据区间的特征交替推进左右端点求解问题,其高效的原因在于避免了大量的无效枚举,其区间枚举都是根据区间特征有方向的枚举,如果胡乱使用尺取法的话会使得枚举量减少,因而很大可能会错误,所以关键的一步是进行问题的分析!
二、折半枚举
折半枚举不是一般的双向搜索,当问题的规模较大,无法枚举所有元素的组合,但能够枚举一半元素的组合,此时将问题拆成两半后分别枚举,再合并它们的结果这一方法往往非常有效。
POJ2785
给定各有n个整数的四个数列A,B,C,D。
从每个数列中各取一个数,使四个数的和为0.当一个数列中有多个相同数字时,把它们作为不同的数字看待。
求出这样的组合的个数。
1≤n≤4000
|(数字的值)|≤228
n=6
A={-45,-41,-36,-36,26,-32}
B={22,-27,53,30,-38,-54}
C={42,56,-37,-75,-10,-6}
D={-16,30,77,-46,62,45}
5
如果全部枚举,则有n4种可能性。
时间复杂度通不过。
因此可以进行折半枚举,计算A,B之间的组合,共有n2种情况,同样的,C,D之间也有n2种情况。
在取出A,B组合中的一组组合(a+b)时,为了使和为0,去查找C,D组合中满足a+b+c+d=0的组合(c+d),这个查找可以用二分查找来实现,因此最后的复杂度是O(n2logn)
string>
typedeflonglongLL;
constintMaxn=4010;
intA[Maxn],B[Maxn],C[Maxn],D[Maxn];
intAB[Maxn*Maxn],CD[Maxn*Maxn];
intn;
LLsolved(intval)
intl=0,r=n*n-1;
LLans;
while(l<
=r)
intmid=(l+r)/2;
if(CD[mid]<
=val)
l=mid+1;
r=mid-1;
ans=r;
l=0,r=n*n-1;
val)
ans=ans-r;
returnans;
while(~sf("
n))
i++)
sf("
%d%d%d%d"
A[i],&
B[i],&
C[i],&
D[i]);
intcnt=0;
for(intj=0;
j<
j++)
AB[cnt]=A[i]+B[j],CD[cnt++]=C[i]+D[j];
sort(CD,CD+cnt);
LLans=0;
ans+=solved(0-AB[i]);
%lld\n"
ans);
POJ3977
给你一个含n(n<
=35)个数的数组,让你在数组中选出一个非空子集,使其元素和的绝对值最小,输出子集元素的个数以及元素和的绝对值,若两个子集元素和相等,输出元素个数小的那个。
思路:
如果直接暴力枚举,复杂度O(2^n),n为35时会超时,故可以考虑折半枚举,利用二进制将和以及元素个数存在两个结构体数组中,先预判两个结构体是否满足题意,再将其中一个元素和取相反数后排序,因为总元素和越接近零越好,再二分查找即可,用lower_bound时考虑查找到的下标和他前一个下标,比较元素和以及元素个数,不断更新即可。
structZ{
longlongintx;
inty;
booloperator<
(constZ&
b)const
if(x!
=b.x)
returnx<
b.x;
returny<
b.y;
}a[300005],b[300005];
longlong
intc[40];
longlongintabs1(longlongintx){
if(x<
0)
return-x;
returnx;
intmain(){
while((cin>
>
n)&
n!
=0){
for(inti=0;
i<
300005;
a[i].x=a[i].y=b[i].x=b[i].y=0;
longlongintsum=1e17;
intans=40;
n;
cin>
c[i];
intn1=n/2;
1<
<
n1;
i++){
for(intj=0;
j<
j++){
if(i>
j&
1&
(i!
=0||j!
=0)){
a[i-1].x+=c[j];
a[i-1].y++;
intn2=n-n1;
(1<
n2);
n2;
b[i-1].x+=c[j+n1];
b[i-1].y++;
n1)-1;
if(abs1(a[i].x)<
sum){
sum=abs1(a[i].x);
ans=a[i].y;
elseif(abs1(a[i].x)==sum&
a[i].y<
ans){
for(inti=0;
a[i].x=-a[i].x;
n2)-1;
if(abs1(b[i].x)<
sum=abs1(b[i].x);
ans=b[i].y;
elseif(abs1(b[i].x)==sum&
b[i].y<
sort(a,a+(1<
n1)-1);
sort(b,b+(1<
n2)-1);
intt=lower_bound(b,b+(1<
n2)-1,a[i])-b;
if(t>
0){
if(abs1(b[t-1].x-a[i].x)<
sum=abs1(b[t-1].x-a[i].x);
ans=b[t-1].y+a[i].y;
elseif(abs1(b[t-1].x-a[i].x)==sum&
b[t-1].y+a[i].y<
if(t<
n2)-1){
if(abs1(b[t].x-a[i].x)<
sum=abs1(b[t].x-a[i].x);
ans=b[t].y+a[i].y;
elseif(abs1(b[t].x-a[i].x)==sum&
b[t].y+a[i].y<
cout<
"
ans<
endl;