NOIP基础算法——贪心和分治
合集下载
相关主题
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
d(st,ed)=d(st,mid)+d(mid+1,ed)+F(st,mid,ed)
其中F(st,mid,ed)表示一个数取自a[st,mid],另一个 数取自a[mid+1,ed]所构成的逆序对数目。
和归并排序一样,划分和递归求解都好办,关键在于合并:如 何求出i在左边,而j在右边的逆序对数目呢?统计的常见技巧是 “分类”。我们按照j的不同把这些“跨越两边”的逆序对进行 分类:只要对于右边的每个j,统计左边比它大的元素个数f(j), 则所有f(j)之和便是答案。
procedure pd(r1,r2:integer;var maxx,minn:integer) begin
var max1,min1,max2,min2,d:integer; if r1=r2 then begin maxx:=x[r1]; minn:=x[r1];end
else if r2=r1+1 then begin if x[r2]>x[r1] then begin maxx:=x[r2];minn:=x[r1];end else begin maxx:=x[r1];minn:=x[r2];end end else begin d:=(r1+r2)/2; pd(r1,d,max1,min1); pd(d+1,r2,max2,min2); if max1>max2 then maxx:=max1;else maxx:=max2; if min1<min2 then minn:=min1;else minn:=min2; end
if(a[i]>a[j])then begin temp[p]:=a[j];inc(p);inc(j);end else begin temp[p]:=a[i];inc(p);inc(i);end
while(i<=mid)do begin temp[p]:=a[i];inc(p);inc(i);end while(j<=right)do begin temp[p]:=a[j];inc(p);inc(i);end for i:=left to right do a[i]:=temp[i]; End;
二、分治法的适用条件
能使用分治法解决的问题,它们一般具备以下几个特征: ①该问题可分解成若干相互独立、规模较小的相同子问题; ②子问题缩小到一定的程度就能轻易得到解; ③子问题的解合并后,能得到原问题的解;
分治法在信息学竞赛中应用非常广泛,使用分治策略能生成 一些常用的算法和数据结构,如快排、最优二叉树、线段树 等;还可以直接使用分治策略,解决一些规模很大、无法直 接下手的问题。
直接使用求根公式,极为复杂。加上本题的提示给我们以 启迪:采用二分法逐渐缩小根的范围,从而得到根的某精 度的数值。
分析
A.当已知区间(a,b)内有一个根时;
用二分法求根,若区间(a,b)内有根,则必有f(a)*f(b)<0。 重复执行如下的过程:
①、若a+0.00001>b或f((a+b)/2)=0,则可确定根为 (a+b)/2并退出过程;
end
2、求方程的根
例题2:一元三次方程求解(NOIP2001)
【题目描述】有形如:ax3+bx2+cx+d=0这样的一个一元三次 方程。给出该方程中各项的系数(a,b,c,d均为实数),并约定该 方程存在三个不同实根(根的范围在-100至100之间),且根与 根之差的绝对值>=1。要求由小到大依次在同一行输出这三个 实根(根与根之间留有空格),并精确到小数点后4位。
方法1:朴素算法
在看完试题以后,我们不难想到一个非常简单的算 法——穷举算法,即对数组中任意的两个元素进行 判断,看它们是不是构成“逆序对”,因此这种算 法的时间复杂度为O(N2)。 c:=0; for i:=1 to n -1 do for j:=i+1 to n do if a[i]>a[j] then c:=c+1;
改为“if(a[i]>a[j])then
begin tot:=tot+mid-i+1;temp[p]:=a[j];inc(p);inc(j);end
4、二分查找
【问题】给出从小到大排列的n个不同数a[1]~a[n], 试判断元素x是否出现在表中。
方法1:顺序查找。即一个一个进行寻找,时间复 杂度为O(n)。这个方法并没有用到“n个数从小到 大排列”这一个关键条件,因而时间效率低下。
核心参考代码
procedure Divide(x1,x2:double) Begin
var x0,y0,y1,y2:double; x0:=(x1+x2)div 2; y1:=cal(x1);y2:=cal(x2);y0:=cal(x0); if(x2-x1<0.00001 and y1*y2<0)then
【文件输入】输入仅一行,有四个数,依次为a、b、c、d
【文件输出】输出也只有一行,即三个根(从小到大输出)
【样例输入】1 -5 -4 20
【样例输入】-2.00 2.00 5.00
分析
如果精确到小数点后两位,可用简单枚举法:将x从100.00 到100.00(步长0.01)逐一枚举,得到20000个 f(x),取其值与0最接近的三个f(x),对应的x即为答案。而 题目已改成精度为小数点后4位,枚举算法时间复杂度将 达不到要求。
begin write((x2+x1)div 2:0:4);exit;end if(y1*y0<0 or x0-x1>1)then divide(x1,x0); if(y0*y2<0 or x2-x0>1)then divide(x0,x2); End;
3、归并排序
归并排序的基本思想:归并排序充分应用分治算法的策略, 通过二分的思想,将n个数最终分成n个单独的有序数列, 每个数列中仅有一个数字;再将相邻的两列数据合并成一 个有序数列;再重复上面的合并操作,直到合成一个有序 数列。按照分治三步法来说: (1)划分:把序列分成元素个数相等的两半; (2)递归求解:把两半分别排序; (3)合并:把两个有序表合成一个有序表;
方法2:二分查找
只需要比较log2n个元素。假设需要在a[L]~a[r]中查找元素x。 划分:检查某个元素a[m](L<=m<=r),如果a[m]=x则查找成
功,返回m。 递归求解:如果a[m]>x,那么元素只可能在a[L]~a[m-1]中
如果a[m]<x,那么元素只可能在a[m+1]~a[r]中。 合并:不需要合并。
【变形1】求逆序对数目
例题3:求“逆序对” 给定一整数数组A=(A1,A2,…An), 若i<j且Ai>Aj,则
<i,j>就为一个逆序对。例如数组(3,1,4,5, 2)的逆序对有<3,1>,<3,2>,<4,2>,<5,2>。问题 是,输入n和A数组,统计逆序对数目。 数据范围:1<=n<=30000。
三、分治的三步骤
①分解:将要解决的问题分解成若干个规 模较小的同类子问题;
②解决:当子问题划分得足够小时,求解 出子问题的解。
③合并:将子问题的解逐层合并成原问题 来自百度文库解。
分治算法设计过程图
在划分问题时,可以采用递归策略,把一个大问题逐 步分解成规模较小的子问题,直至可以直接求出子问 题的解;再将子问题逐层合并,返回到顶层,得到原 问题的解。
NOIP基础算法——分治与贪心
第五部分
分治策略
一、分治思想
分治法,又叫分治策略,顾名思义,分而治之。
它的基本思想:对于难以直接解决的规模较大的 问题,把它分解成若干个能直接解决的相互独立 的子问题,递归求出各子问题的解,再合并子问 题的解,得到原问题的解。
通过减少问题的规模,逐步求解,能够明显降低 解决问题的复杂度。
分析
B、求方程的所有三个实根
所有的根的范围都在-100至100之间,且根与根之差的绝 对值>=1。因此可知:在[-100,-99]、[-99,-98]、……、[99, 100]、[100,100]这201个区间内,每个区间内至多只能 有一个根。即:除区间[100,100]外,其余区间[a,a+1], 只有当f(a)=0或f(a)*f(a+1)<0时,方程在此区间内才有解。 若f(a)=0 ,解即为a;若f(a)*f(a+1)<0 ,则可以利用A中所 述的二分法迅速出找出解。如此可求出方程的所有的解。
根据分治策略的划分原则,把原问题划分成多少个子 问题才合适呢?各个子问题的规模应该多大才合适呢?
一般来说,每次划分成2个子问题,每个子问题的规 模差不多最合适。合并解时要因题而异,有些问题递 归分解完能直接得到原问题的解,有些问题需逐层合 并,得到原问题的解。
四、分治的框架结构
procedure Divide() begin
分析
显然,前两部分是很容易完成的,关键在于如何把两个有 序表合成一个。每次只需要把两个有序表中当前的最小元 素加以比较,删除较小元素并加入合并后的新表中。
核心参考代码
procedure MergeSort(left,right:integer)//归并排序 begin
if left=right then exit; //只有一个元素 mid:=(left+right)div 2; //找中间位 MergeSort(left,mid); //对左边归并 MergeSort(mid+1,right); //对右边归并 i:=left;j:=mid+1,p:=left; //合并左右 while(i<=mid and j<=right)do
②、若f(a)*f((a+b)/2)<0,则由题目给出的定理可知根在区 间(a,(a+b)/2)中,故对区间重复该过程;
③、若f(a)*f((a+b)/2)>0,则必然有f((a+b)/2)*f(b)<0,根在 ((a+b)/2,b)中,对此区间重复该过程。
执行完毕,就可以得到精确到0.0001的根。
幸运的是,归并排序可以帮助我们“顺便”完成f(j)的计算:由 于合并操作是从小到大进行排序的,当右边的a[j]复制到T中时, 左边还没有来得及复制到T的那些数就是左边所有比a[j]大的数。 此时累加器中加上左边元素个数mid-i+1即可。
即把“if(a[i]>a[j])then begin temp[p]:=a[j];inc(p);inc(j);end
使用这一算法,比较次数为2(n-1)。若n=10,则比较18次。
【方法2】分治策略
划分:把n个数均分为两半。即:划分点为 d=(r1+r2)/2,两个区间为[r1,d]和[d+1,r2]。
递归求解:求左半的最小值min1 和最大值max1 以及右半最小值min2和最大值max2。
合并:max1与max2比较得到所有数的最大值为 maxx; min1与min2比较得到所有数的最小值为 minn。
1、求最大值和最小值
例题1:给n个数,求它们之中最大值和最小值,要求比 较次数尽量小。
分析:假设数据个数为n,存放在数组a[1..n]中。可以直接进 行比较:
minn:=a[1];maxx:=a[1]; for i:=2 to n do
if a[i]>maxx then maxx:=a[i]; else if a[i]<minn then minn:=a[i];
时间效率不尽如人意….. 问题出现在哪里呢??
求逆序对的方法:
求逆序对有多种方法, 目前使用比较广泛且实现 比较简单的主要有三种算法: 1、归并排序 2、线段树 3、树状数组
方法2:分治策略
采用二分法求解: 记数列a[st,ed]的逆序对数目为d(st,ed);
mid=[(st+ed)/2],则有:
if(问题不可分)then//解决 begin 直接求解; 返回问题的解;
end else begin
对原问题进行分治;//分解 递归对每一个分治的部分进行求解; //解决 归并整个问题,得出全问题的解; //合并 end end;
五、分治的典型应用
1、求最大值和最小值 2、求方程的根 3、二分查找 4、归并排序 5、快速幂 6、求解线性递推关系 7、棋盘覆盖问题 8、循环日程表问题 9、寻找最近点对
其中F(st,mid,ed)表示一个数取自a[st,mid],另一个 数取自a[mid+1,ed]所构成的逆序对数目。
和归并排序一样,划分和递归求解都好办,关键在于合并:如 何求出i在左边,而j在右边的逆序对数目呢?统计的常见技巧是 “分类”。我们按照j的不同把这些“跨越两边”的逆序对进行 分类:只要对于右边的每个j,统计左边比它大的元素个数f(j), 则所有f(j)之和便是答案。
procedure pd(r1,r2:integer;var maxx,minn:integer) begin
var max1,min1,max2,min2,d:integer; if r1=r2 then begin maxx:=x[r1]; minn:=x[r1];end
else if r2=r1+1 then begin if x[r2]>x[r1] then begin maxx:=x[r2];minn:=x[r1];end else begin maxx:=x[r1];minn:=x[r2];end end else begin d:=(r1+r2)/2; pd(r1,d,max1,min1); pd(d+1,r2,max2,min2); if max1>max2 then maxx:=max1;else maxx:=max2; if min1<min2 then minn:=min1;else minn:=min2; end
if(a[i]>a[j])then begin temp[p]:=a[j];inc(p);inc(j);end else begin temp[p]:=a[i];inc(p);inc(i);end
while(i<=mid)do begin temp[p]:=a[i];inc(p);inc(i);end while(j<=right)do begin temp[p]:=a[j];inc(p);inc(i);end for i:=left to right do a[i]:=temp[i]; End;
二、分治法的适用条件
能使用分治法解决的问题,它们一般具备以下几个特征: ①该问题可分解成若干相互独立、规模较小的相同子问题; ②子问题缩小到一定的程度就能轻易得到解; ③子问题的解合并后,能得到原问题的解;
分治法在信息学竞赛中应用非常广泛,使用分治策略能生成 一些常用的算法和数据结构,如快排、最优二叉树、线段树 等;还可以直接使用分治策略,解决一些规模很大、无法直 接下手的问题。
直接使用求根公式,极为复杂。加上本题的提示给我们以 启迪:采用二分法逐渐缩小根的范围,从而得到根的某精 度的数值。
分析
A.当已知区间(a,b)内有一个根时;
用二分法求根,若区间(a,b)内有根,则必有f(a)*f(b)<0。 重复执行如下的过程:
①、若a+0.00001>b或f((a+b)/2)=0,则可确定根为 (a+b)/2并退出过程;
end
2、求方程的根
例题2:一元三次方程求解(NOIP2001)
【题目描述】有形如:ax3+bx2+cx+d=0这样的一个一元三次 方程。给出该方程中各项的系数(a,b,c,d均为实数),并约定该 方程存在三个不同实根(根的范围在-100至100之间),且根与 根之差的绝对值>=1。要求由小到大依次在同一行输出这三个 实根(根与根之间留有空格),并精确到小数点后4位。
方法1:朴素算法
在看完试题以后,我们不难想到一个非常简单的算 法——穷举算法,即对数组中任意的两个元素进行 判断,看它们是不是构成“逆序对”,因此这种算 法的时间复杂度为O(N2)。 c:=0; for i:=1 to n -1 do for j:=i+1 to n do if a[i]>a[j] then c:=c+1;
改为“if(a[i]>a[j])then
begin tot:=tot+mid-i+1;temp[p]:=a[j];inc(p);inc(j);end
4、二分查找
【问题】给出从小到大排列的n个不同数a[1]~a[n], 试判断元素x是否出现在表中。
方法1:顺序查找。即一个一个进行寻找,时间复 杂度为O(n)。这个方法并没有用到“n个数从小到 大排列”这一个关键条件,因而时间效率低下。
核心参考代码
procedure Divide(x1,x2:double) Begin
var x0,y0,y1,y2:double; x0:=(x1+x2)div 2; y1:=cal(x1);y2:=cal(x2);y0:=cal(x0); if(x2-x1<0.00001 and y1*y2<0)then
【文件输入】输入仅一行,有四个数,依次为a、b、c、d
【文件输出】输出也只有一行,即三个根(从小到大输出)
【样例输入】1 -5 -4 20
【样例输入】-2.00 2.00 5.00
分析
如果精确到小数点后两位,可用简单枚举法:将x从100.00 到100.00(步长0.01)逐一枚举,得到20000个 f(x),取其值与0最接近的三个f(x),对应的x即为答案。而 题目已改成精度为小数点后4位,枚举算法时间复杂度将 达不到要求。
begin write((x2+x1)div 2:0:4);exit;end if(y1*y0<0 or x0-x1>1)then divide(x1,x0); if(y0*y2<0 or x2-x0>1)then divide(x0,x2); End;
3、归并排序
归并排序的基本思想:归并排序充分应用分治算法的策略, 通过二分的思想,将n个数最终分成n个单独的有序数列, 每个数列中仅有一个数字;再将相邻的两列数据合并成一 个有序数列;再重复上面的合并操作,直到合成一个有序 数列。按照分治三步法来说: (1)划分:把序列分成元素个数相等的两半; (2)递归求解:把两半分别排序; (3)合并:把两个有序表合成一个有序表;
方法2:二分查找
只需要比较log2n个元素。假设需要在a[L]~a[r]中查找元素x。 划分:检查某个元素a[m](L<=m<=r),如果a[m]=x则查找成
功,返回m。 递归求解:如果a[m]>x,那么元素只可能在a[L]~a[m-1]中
如果a[m]<x,那么元素只可能在a[m+1]~a[r]中。 合并:不需要合并。
【变形1】求逆序对数目
例题3:求“逆序对” 给定一整数数组A=(A1,A2,…An), 若i<j且Ai>Aj,则
<i,j>就为一个逆序对。例如数组(3,1,4,5, 2)的逆序对有<3,1>,<3,2>,<4,2>,<5,2>。问题 是,输入n和A数组,统计逆序对数目。 数据范围:1<=n<=30000。
三、分治的三步骤
①分解:将要解决的问题分解成若干个规 模较小的同类子问题;
②解决:当子问题划分得足够小时,求解 出子问题的解。
③合并:将子问题的解逐层合并成原问题 来自百度文库解。
分治算法设计过程图
在划分问题时,可以采用递归策略,把一个大问题逐 步分解成规模较小的子问题,直至可以直接求出子问 题的解;再将子问题逐层合并,返回到顶层,得到原 问题的解。
NOIP基础算法——分治与贪心
第五部分
分治策略
一、分治思想
分治法,又叫分治策略,顾名思义,分而治之。
它的基本思想:对于难以直接解决的规模较大的 问题,把它分解成若干个能直接解决的相互独立 的子问题,递归求出各子问题的解,再合并子问 题的解,得到原问题的解。
通过减少问题的规模,逐步求解,能够明显降低 解决问题的复杂度。
分析
B、求方程的所有三个实根
所有的根的范围都在-100至100之间,且根与根之差的绝 对值>=1。因此可知:在[-100,-99]、[-99,-98]、……、[99, 100]、[100,100]这201个区间内,每个区间内至多只能 有一个根。即:除区间[100,100]外,其余区间[a,a+1], 只有当f(a)=0或f(a)*f(a+1)<0时,方程在此区间内才有解。 若f(a)=0 ,解即为a;若f(a)*f(a+1)<0 ,则可以利用A中所 述的二分法迅速出找出解。如此可求出方程的所有的解。
根据分治策略的划分原则,把原问题划分成多少个子 问题才合适呢?各个子问题的规模应该多大才合适呢?
一般来说,每次划分成2个子问题,每个子问题的规 模差不多最合适。合并解时要因题而异,有些问题递 归分解完能直接得到原问题的解,有些问题需逐层合 并,得到原问题的解。
四、分治的框架结构
procedure Divide() begin
分析
显然,前两部分是很容易完成的,关键在于如何把两个有 序表合成一个。每次只需要把两个有序表中当前的最小元 素加以比较,删除较小元素并加入合并后的新表中。
核心参考代码
procedure MergeSort(left,right:integer)//归并排序 begin
if left=right then exit; //只有一个元素 mid:=(left+right)div 2; //找中间位 MergeSort(left,mid); //对左边归并 MergeSort(mid+1,right); //对右边归并 i:=left;j:=mid+1,p:=left; //合并左右 while(i<=mid and j<=right)do
②、若f(a)*f((a+b)/2)<0,则由题目给出的定理可知根在区 间(a,(a+b)/2)中,故对区间重复该过程;
③、若f(a)*f((a+b)/2)>0,则必然有f((a+b)/2)*f(b)<0,根在 ((a+b)/2,b)中,对此区间重复该过程。
执行完毕,就可以得到精确到0.0001的根。
幸运的是,归并排序可以帮助我们“顺便”完成f(j)的计算:由 于合并操作是从小到大进行排序的,当右边的a[j]复制到T中时, 左边还没有来得及复制到T的那些数就是左边所有比a[j]大的数。 此时累加器中加上左边元素个数mid-i+1即可。
即把“if(a[i]>a[j])then begin temp[p]:=a[j];inc(p);inc(j);end
使用这一算法,比较次数为2(n-1)。若n=10,则比较18次。
【方法2】分治策略
划分:把n个数均分为两半。即:划分点为 d=(r1+r2)/2,两个区间为[r1,d]和[d+1,r2]。
递归求解:求左半的最小值min1 和最大值max1 以及右半最小值min2和最大值max2。
合并:max1与max2比较得到所有数的最大值为 maxx; min1与min2比较得到所有数的最小值为 minn。
1、求最大值和最小值
例题1:给n个数,求它们之中最大值和最小值,要求比 较次数尽量小。
分析:假设数据个数为n,存放在数组a[1..n]中。可以直接进 行比较:
minn:=a[1];maxx:=a[1]; for i:=2 to n do
if a[i]>maxx then maxx:=a[i]; else if a[i]<minn then minn:=a[i];
时间效率不尽如人意….. 问题出现在哪里呢??
求逆序对的方法:
求逆序对有多种方法, 目前使用比较广泛且实现 比较简单的主要有三种算法: 1、归并排序 2、线段树 3、树状数组
方法2:分治策略
采用二分法求解: 记数列a[st,ed]的逆序对数目为d(st,ed);
mid=[(st+ed)/2],则有:
if(问题不可分)then//解决 begin 直接求解; 返回问题的解;
end else begin
对原问题进行分治;//分解 递归对每一个分治的部分进行求解; //解决 归并整个问题,得出全问题的解; //合并 end end;
五、分治的典型应用
1、求最大值和最小值 2、求方程的根 3、二分查找 4、归并排序 5、快速幂 6、求解线性递推关系 7、棋盘覆盖问题 8、循环日程表问题 9、寻找最近点对