NOIP基础算法讲解2

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
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;
数据范围:n≤10^6。所有数之和不超过10^9。
例题8:快速幂
【问题】计算an mod k的值 ,其中n<=109。
方法1:朴素算法。每次乘以a,时间复杂度O(n)。 function power(a,n:longint):longint; var x:longint; begin x:=1; for i:=1 to n do x:=x*a; power:=x; end;
时间效率不尽如人意….. 问题出现在哪里呢??
方法2:分治策略
采用分治求解: ➢划分问题:把序列分成元素个数尽量相等的两半; ➢递归求解:统计i和j均在左边或者均在右边的逆序对个数; ➢合并问题:统计i在左边,但j在右边的逆序对个数。
记数列a[st]~a[ed]的逆序对数目为d(st,ed); mid=(st+ed)/2,则有:
三、分治的三步骤
①划分问题:将要解决的问题分解成若干个规模较 小的同类子问题;
②递归求解:当子问题划分得足够小时,求解出子 问题的解。
③合并问题:将子问题的解逐层合并成原问题的解。
四、分治的框架结构
procedure Divide() begin
if 问题不可分 then //直接解决 begin 直接求解; 返回问题的解; end
答案是肯定的。假设在快速排序的“划分”结束后,数组 A[s..t]被分成了A[s..x]和A[x+1..t],则可以根据左边的元素 个数x-s+1和k的大小关系,确定第k小的数在左边或右边, 然后递归求解即可。
可以证明,在期望意义下,程序的时间复杂度为O(n)。
例题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数组,统计逆序对数目。
很明显,此程序要超时。
方法2:分治算法
a2
andiv2 * andiv2
a
(
n1)
div
2
*
a(n1)div 2
*a
当n为偶数时 当n为奇数时
划分问题:如果n是偶数,则考虑x=n div 2 否则考虑x=(n-1) div 2
递归求解:计算ax。 合并问题:如果n是偶数,则an=(ax)2,否则an=(ax)2*a
1.5*10^9
题目分析
begin readln(n); for i:=1 to n do read(a[i]); qsort(1,n); //按照从小到大快排 j:=1; for i:=2 to n+1 do //顺序扫描 if a[i]=a[i-1] then inc(j) else begin writeln(a[i-1],' ',j);j:=1;end;
求逆序对的方法:
求逆序对有多种方法, 目前使用比较广泛且实现 比较简单的主要有三种算法: 1、归并排序 2、线段树 3、树状数组
时间复杂度为O(nlogn)
练习:超车3063
有n辆车在同一个无限长的直道上匀速行驶,给出每辆车行 驶的速度和初始位置。任意两辆车的位置都不相同。如果 有两辆车A车和B车,A车在B车的后面,并且A车的速度比 B车的快,那么经过一段时间后,A车一定会超过B车。我 们称之为一次超车。那么请你计算超车总数。
N<=10000,M<=50
扩展知识:
下面提一个有趣的问题:如果数组中有多个元素都是x, 现在要查找x,则上面函数的返回的是哪一个的下标呢?
第一个?最后一个?都不是。 若在原数组中存在多个元素x,查找时返回它出现的第一
个位置。如果不存在,返回这样一个下标i:在此处插入x (原来的元素A[i]、A[i+1],…全部往后移动一个位置)后 序列仍然有序。
NOIP基础算法讲解
分治策略
一、分治思想
分治法,又叫分治策略,顾名思义,分而治之。
它的基本思想:对于难以直接解决的规模较大的 问题,把它分解成若干个能直接解决的相互独立 的子问题,递归求出各子问题的解,再合并子问 题的解,得到原问题的解。
通过减少问题的规模,逐步求解,能够明显降低 解决问题的复杂度。
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]所构成的逆序对数目。
方法2:分治策略
和归并排序一样,划分和递归求解都好办,关键在于合并:如 何求出i在左边,而j在右边的逆序对数目呢?统计的常见技巧是 “分类”。我们按照j的不同把这些 “跨越两边” 的逆序对进行 分类:只要对于右边的每个j,统计左边比它大的元素个数f(j), 则所有f(j)之和便是答案。
【扩展2】二分查找求上界,即最后一次出现位置的后一个位置
function Erfen(L,r,x:longint):longint; var mid:longint; begin
while(L<r)do begin
mid:=(L+r)div 2; if a[mid]<=x then L:=mid+1
【扩展1】二分查找求下界,即第一次出现的位置
function Erfen(L,r,x:longint):longint; var mid:longint; begin
while(L<r)do begin
mid:=(L+r)div 2; if a[mid]>=x then r:=mid
else L:=mid+1; end; Erfen:= L; end;
end.
例题2:第K小的数
输入n个整数和一个正整数k(1≤k≤n),输入这些 整数从小到大排序后的第k个(例如,k=1就是最小 值)。n≤10^7。
题目分析
选第k小的数,最显然的方法是先排序,然后直接输出下 标为k的元素,但10^7的规模即使对于O(nlogn)的算法来 说稍微大了点。有没有更快的方法呢?
数据范围:1<=n<=1000000。
方法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;
方法2:分治算法
根据这个方法很容易写出程序: function power(a,n:longint):longint; begin
else begin 对原问题进行分治;//划分问题 递归对每一个分治的部分进行求解; //递归求解 归并整个问题,得出全问题的解; //合并问题
end; end;
五、分治的典型应用
1、求非线性方程的根 2、快速排序 3、归并排序 4、二分查找 5、快速幂 6、求解线性递推关系 7、棋盘覆盖问题 8、循环日程表问题
改为“if(a[i]>a[j])then
begin tot:=tot+mid-i+1;temp[p]:=a[j];inc(p);inc(j);end
题目分析
procedure MergeSort(left,right:longint)//归并排序 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
元素0,可以用二分查找;
例题7:最大值最小化2976
把一个包含n个正整数的序列划分成m个连续的子序列 (每个正整数恰好属于一个序列)。设第i个序列的 各数之和为S(i),你的任务是让所有S(i)的最大值尽 量小。
例如序列:1 2 3 2 5 4,划分成3个序列的最优方案 为1 2 3|2 5|4,其中S(1)、S(2)、S(3)分别为6、7、 4,最大值为7;如果划分成1 2|3 2|5 4,最大值为9, 不如刚才好。
例题1:统计数字(NOIP2008)2000
给你n个自然数,每个数均不超过1.5*10^9。已知不相同 的数不会超过10000个,现在需要统计这些自然数各自出 现的个数,并按照从小到大的顺序输出统计结果。
数据范围: 40%的数据满足1<=n<=1000 80%的数据满足1<=n<=50000 100%的数据满足1<=n<=200000,每个数均不超过
else r:=mid; end; Erfen:= L; end;
例题5:范围统计
给出n个整数和m个询问,对于每个询问(a,b),输 出在n个整数中,值在区间[a,b]内的整数个数。
算法步骤:
➢ ①先将n个整数从小到大排序; ➢ ②找大于等于a的第一个元素的下标L,即求a的下界; ➢ ③找小于等于b的最后一个元素的下一个下标R,即b的上界 ➢ ④Ans=R-L
数据规模 对于20%的数据,n<=300; 对于50%的数据,n<=3000; 对于100%的数据,n<=300000;
例题4:班级排名1904
给你HZH班上N名学生的姓名(字符串),然后有M次 考试,每次考试的成绩也给你(形如:姓名 成绩)。
请你求出HZH每次考试后的名次。分数是累加的, 每次考试后,HZH的名次都有可能变动(名次取决 于前面所有考试的总分)。如果HZH和另一些同学 分数相等,他永远排在这些同学的前面。 对于并列 问题,采取以下方法处理: 如果A与B并列第一名,C 只比A、B的成绩低,那么C是第三名。
二、分治法的适用条件
能使用分治法解决的问题,它们一般具备以下几个特征: ①该问题可分解成若干相互独立、规模较小的相同子问题; ②子问题缩小到一定的程度就能轻易得到解; ③子问题的解合并后,能得到原问题的解;
分治法在信息学竞赛中应用非常广泛,使用分治策略能生成 一些常用的算法和数据结构,如快排、最优二叉树、线段树 等;还可以直接使用分治策略,解决一些规模很大、无法直 接下手的问题。
幸运的是,归并排序可以帮助我们“顺便”完成f(j)的计算:由 于合并操作是从小到大进行排序的,当右边的a[j]复制到temp中 时,左边还没有来得及复制到temp的那些数就是左边所有比a[j] 大的数。此时累加器中加上左边元素个数mid-i+1即可。
即把“if(a[i]>a[j])then begin temp[p]:=a[j];inc(p);inc(j);end
例题6:查找等值点
有n个不同整数从小到大排序后放在数组 A1~An中,是否存在i,使得Ai=i?若存在, 试找到此点。
题目分析
由于所有整数都不同,而且A以升序排列,因此 Ai+1-Ai>=1
取Bi=Ai-i,则Ai=i当且仅当Bi=0 Bi+1-Bi=Ai+1-(i+1)-(Ai-i)=Ai+1-Ai-1>=0 因此B也是从小到大排序的,在有序序列中查找
A[mid]和x的各种关系所带来的影响如下: ①.A[mid]=x:至少已经找到一个,而左边可能还有,因此区间变为[L,mid]。 ②.A[mid]>x:所求位置不可能在后面,但有可能是mid,因此区间变为
[L,mid]。 ③.A[mid]<x:m和前面都不可行,因此区间变为[mid+1,y]。
合并一下:A[mid]≥x时新区间为[L,mid];A[m]<x时新区间为[mid+1,r]。
相关文档
最新文档