NOIP复赛复习14尺取法与折半枚举_第1页
NOIP复赛复习14尺取法与折半枚举_第2页
NOIP复赛复习14尺取法与折半枚举_第3页
NOIP复赛复习14尺取法与折半枚举_第4页
NOIP复赛复习14尺取法与折半枚举_第5页
已阅读5页,还剩14页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

NOIP复赛复习14尺取法与折半枚举一、尺取法尺取法:顾名思义,像尺子一样取一段,借用挑战书上面的话说,尺取法通常是对数组保存一对下标,即所选取的区间的左右端点,然后根据实际情况不断地推进区间左右端点以得出答案。之所以需要掌握这个技巧,是因为尺取法比直接暴力枚举区间效率高很多,尤其是数据量大的时候,所以尺取法是一种高效的枚举区间的方法,一般用于求取有一定限制的区间个数或最短的区间等等。当然任何技巧都存在其不足的地方,有些情况下尺取法不可行,无法得出正确答案。使用尺取法时应清楚以下四点:1、什么情况下能使用尺取法?2、何时推进区间的端点?3、如何推进区间的端点?4、何时结束区间的枚举?尺取法通常适用于选取区间有一定规律,或者说所选取的区间有一定的变化趋势的情况,通俗地说,在对所选取区间进行判断之后,我们可以明确如何进一步有方向地推进区间端点以求解满足条件的区间,如果已经判断了目前所选取的区间,但却无法确定所要求解的区间如何进一步得到根据其端点得到,那么尺取法便是不可行的。首先,明确题目所需要求解的量之后,区间左右端点一般从最整个数组的起点开始,之后判断区间是否符合条件在根据实际情况变化区间的端点求解答案。POJ 3061给定长度为n的数列整数a0,a1,an-1以及证书S。求出总和不小于S的连续子序列的长度的最小值。如果解不存在在,则输出0.已知:10n1050ai104S108sampleinputn= 10S= 15a= 5,1,3,5,10,7,4,9,2,8sampleoutput2(5 + 10)我们设以as开始总和最初大于S时的连续子序列as+.+at1,这时as+1+.+at2as+.+at2S所以从as+1开始总和最初超过S的连续子序列如果是as+1+.+at1的话,则必然有tt。用下面的图来解释比较清晰:#include#include#include#include#define sf scanf#define pf printfusing name space std;const int Maxn = 100010;int T,n,s;int sumMaxn;int main() int a; sf(%d,&T); while(T-) int tail = -1,head = -1; sf(%d%d,&n,&s); for(int i = 0;i = s) tail = i; if(tail = -1) pf(0n); continue; int Min = n; while(head =s) head +; Min = min(Min,tail - head); else if(tail n - 1) tail+; else break; pf(%dn,Min); return 0;POJ 3320题意:一本书有P页,每一页都一个知识点,求去最少的连续页数覆盖所有的知识点。分析:和上面的题一样的思路,如果一个区间的子区间满足条件,那么在区间推进到该处时,右端点会固定,左端点会向右移动到其子区间,且其子区间会是更短的,只是需要存储所选取的区间的知识点的数量,那么使用map进行映射以快速判断是否所选取的页数是否覆盖了所有的知识点。#include#include#include#include#include#define MAX 1000010#define LL long long#define INF 0x3f3f3f3fusing name space std;int aMAX;map cnt;set t;int p, ans = INF, st, en, sum;int main() scanf(%d, &p); for (int i = 0; i p; i+)scanf(%d, a+i), t.insert(ai); int num = t.size(); while (1) while (enp &sumnum) if (cntaen+ = 0)sum+; if (sum num) break; ans = min(ans, en-st); if (-cntast+ = 0) sum-; printf(%dn, ans); return 0;POJ 2566题意:给定一个数组和一个值t,求一个子区间使得其和的绝对值与t的差值最小,如果存在多个,任意解都可行。分析:明显,借用第一题的思路,既然要找到一个子区间使得和最接近t的话,那么不断地找比当前区间的和更大的区间,如果区间和已经大于等于t了,那么不需要在去找更大的区间了,因为其和与t的差值更大,然后区间左端点向右移动推进即可。所以,首先根据计算出所有的区间和,排序之后按照上面的思路求解即可。#include#include#include#define INF 0x3f3f3f3f#define LL long long#define MAX 100010using name space std;typedef pair p;LL aMAX, t, ans, tmp, b;int n, k, l, u, st, en;p sumMAX;LL myabs(LL x) return x=0? x:-x;int main() while (scanf(%d %d, &n,&k), n+k) sum0 = p(0, 0); for (int i = 1; i = n; i+) scanf(%I64d, a+i); sumi = p(sumi-1.first+ai,i); sort(sum, sum+1+n); while (k-) scanf(%I64d,&t); tmp = INF; st = 0, en = 1; while(en = n) b =sumen.first-sumst.first; if(myabs(t-b) t) st+; else if(b t) en+; else break; if(st = en) en+; if (u l) swap(u, l); printf(%I64d %d %dn,ans, l+1, u); return 0;总结:尺取法的模型便是这样:根据区间的特征交替推进左右端点求解问题,其高效的原因在于避免了大量的无效枚举,其区间枚举都是根据区间特征有方向的枚举,如果胡乱使用尺取法的话会使得枚举量减少,因而很大可能会错误,所以关键的一步是进行问题的分析!二、折半枚举折半枚举不是一般的双向搜索,当问题的规模较大,无法枚举所有元素的组合,但能够枚举一半元素的组合,此时将问题拆成两半后分别枚举,再合并它们的结果这一方法往往非常有效。POJ 2785给定各有n个整数的四个数列A,B,C,D。从每个数列中各取一个数,使四个数的和为0.当一个数列中有多个相同数字时,把它们作为不同的数字看待。求出这样的组合的个数。已知:1n4000|(数字的值)|228sampleinputn= 6A= -45,-41,-36,-36,26,-32B= 22,-27,53,30,-38,-54C= 42,56,-37,-75,-10,-6D= -16,30,77,-46,62,45sampleoutput5如果全部枚举,则有n4种可能性。时间复杂度通不过。因此可以进行折半枚举,计算A,B之间的组合,共有n2种情况,同样的,C,D之间也有n2种情况。在取出A,B组合中的一组组合(a + b)时,为了使和为0,去查找C,D组合中满足a + b+c+d = 0的组合(c+d),这个查找可以用二分查找来实现,因此最后的复杂度是O(n2logn)#include#include#include#include#include#include#define sf scanf#define pf printfusing name space std;typedef long long LL;const int Maxn = 4010;int AMaxn,BMaxn,CMaxn,DMaxn;int ABMaxn * Maxn,CDMaxn * Maxn;int n;LL solved(int val) int l = 0, r = n * n - 1; LL ans; while(l = r) int mid = (l + r) / 2; if(CDmid = val) l = mid + 1; else r = mid - 1; ans = r; l = 0, r = n * n - 1; while(l = r) int mid = (l + r) / 2; if(CDmid val) l = mid + 1; else r = mid - 1; ans = ans - r; return ans;int main() while(sf(%d,&n) for(int i = 0;i n;i+)sf(%d%d%d%d,&Ai,&Bi,&Ci,&Di); int cnt = 0; for(int i = 0;i n;i +) for(int j = 0;j n;j +) ABcnt = Ai + Bj,CDcnt+= Ci + Dj; sort(CD,CD + cnt); LL ans = 0; for(int i = 0;i cnt;i +) ans += solved(0 - ABi); pf(%lldn,ans); return 0;POJ 3977题意:给你一个含n(n=35)个数的数组,让你在数组中选出一个非空子集,使其元素和的绝对值最小,输出子集元素的个数以及元素和的绝对值,若两个子集元素和相等,输出元素个数小的那个。思路:如果直接暴力枚举,复杂度O(2n),n为35时会超时,故可以考虑折半枚举,利用二进制将和以及元素个数存在两个结构体数组中,先预判两个结构体是否满足题意,再将其中一个元素和取相反数后排序,因为总元素和越接近零越好,再二分查找即可,用lower_bound时考虑查找到的下标和他前一个下标,比较元素和以及元素个数,不断更新即可。#include#include#include#includeusing name space std;struct Zlong long int x;int y;bool operator (const Z& b)const if(x!=b.x) return x b.x; return yb.y; a300005,b300005;long long int c40;long long int abs1(long long int x)if(xn)&n!=0) for(int i=0;i300005;i+) ai.x=ai.y=bi.x=bi.y=0; long long int sum=1e17; int ans=40; for(int i=0;ici; int n1=n/2; for(int i=0;i1n1;i+) for(int j=0;jj&1&(i!=0|j!=0) ai-1.x+=cj; ai-1.y+; int n2=n-n1; for(int i=0;i(1n2);i+) for(int j=0;jj&1&(i!=0|j!=0) bi-1.x+=cj+n1; bi-1.y+; for(int i=0;i(1n1)-1;i+) if(abs1(ai.x)sum) sum=abs1(ai.x); ans=ai.y; elseif(abs1(ai.x)=sum&ai.yans) ans=ai.y; sum=abs1(ai.x); for(inti=0;i(1n1)-1;i+) ai.x=-ai.x; for(int i=0;i(1n2)-1;i+) if(abs1(bi.x)sum) sum=abs1(bi.x); ans=bi.y; else if(abs1(bi.x)=sum&bi.yans) ans=bi.y; sum=abs1(bi.x); sort(a,a+(1n1)-1); sort(b,b+(1n2)-1); for(int i=0;i(1n1)-1;i+) int t=lower_bound(b,b+(10) if(abs1(bt-1.x-ai.x)sum) sum=abs1(bt-1.x-ai.x);

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

最新文档

评论

0/150

提交评论