51 nod 1056 最长等差数列 V2
2017-10-15 17:56
274 查看
1056 最长等差数列 V2
基准时间限制:8 秒 空间限制:131072 KB 分值: 1280 难度:9级算法题
N个不同的正整数,从中选出一些数组成等差数列。
例如:1 3 5 6 8 9 10 12 13 14
等差子数列包括(仅包括两项的不列举)
1 3 5
1 5 9 13
3 6 9 12
3 8 13
5 9 13
6 8 10 12 14
其中6 8 10 12 14最长,长度为5。
现在给出N个数,你来从中找出一个长度 >= 200 的等差数列,如果没有,输出No Solution,如果存在多个,输出最长的那个的长度。
Input
Output
Input示例
Output示例
提供一个可能是正解的做法吧(源自本题讨论区),也就是@李陶冶 提到的分治做法,来自Jeff Erickson在1999年发表的一篇短论文 Finding longest arithmetic progressions 。
这个论文的做法基于一个结论:大小为n的集合里长度至少为k的不同等差数列的数量是O(n*n/k*k)的。从这个结论入手可以尝试构造出答案。
首先要明确等差数列的定义。
一个等差数列可以用首项、公差和项数的三元组(first,delta,length)表示,当然这个first也可以换成末项last,取决于你的算法。
我们要考虑的等差数列要尽量用少的信息表示所有的可能,也就是说提取出来的等差数列之间不能有相互包含的关系,例如(2,3,4)和(2,3,5)就冗余了,而(2,3,4)和(5,3,4)也是冗余的,因为当它们出现在同一个序列里时,(2,3,5)也是存在的。
因此我们只需要考虑满足(first-delta)这个数字不存在,并且(last+delta)这个数字不存在的等差数列。
接下来要发现这样的等差数列在k充分大时是不多的。
定义集合里的两个元素是足够相邻的,当且仅当它们的排名之差不超过n/(k-1)。由鸽巢原理可知,一个长度为k的等差数列必然包含至少(k-1)/2对足够相邻的元素(考虑等差数列里排名最小的和排名最大的那两个元素)。
而集合里足够相邻的元素对数不超过n*n/2(k-1),我们所求的不同等差数列也不会包含相同的一对足够相邻的元素,所以我们能得到的等差数列数量是不超过n*n/(k-1)*(k-1)的。
最后要利用这个性质来构造相应的算法。
利用数量不多的性质,我们可以尝试找出所有长度至少为 200 的等差数列,从中选取最长的那个。
注意到上面的证明利用到了集合的大小,类似地也可以证明,这样的等差数列必然有⌊k/2⌋项在前⌊n/2⌋小的元素集合里,或者有⌊k/2⌋项在前⌈n/2⌉大的元素集合里。而集合大小减半同时数列项数减半对数列数量的影响是不大的,所以可以尝试对集合分治,分别找出更小情况的解,然后放到大的集合里尝试向左向右O(k)扩张,再进行去重,从而得到当前情况的解。
不妨设T(n,k)表示从大小为n的集合里提取长度至少为k的不同等差数列的复杂度,则有T(n,k)=2T(n/2,k/2)+O((n*n/k*k)k)⇒T(n,k)=O(n*n/k
log n/k)。
实现上可能要注意几个细节:
1. 需要实现原集合的hash来保证扩张的复杂度。
2. 当k=O(log n)时,可以采用O(n*n)的dp直接计算,效率更高,此时递归层数是O(log k)的。
3. k实际上可以动态变化,从而优化常数,本题数据好像不太强,稍微写错点地方也不会出问题。
4. 论文中提到寻找最长等差数列的时候依次选取k=n,n/2,n/4,…,对于本题可以直接选取k=200。
———————————————————————————————————————————————
但因为种种原因,本题可以玄学搜索+各种剪枝用O(n*n)的时间复杂度完成。搜索方法:枚举第一个数,然后枚举公差,用hash判断可行性。
Code:
基准时间限制:8 秒 空间限制:131072 KB 分值: 1280 难度:9级算法题
N个不同的正整数,从中选出一些数组成等差数列。
例如:1 3 5 6 8 9 10 12 13 14
等差子数列包括(仅包括两项的不列举)
1 3 5
1 5 9 13
3 6 9 12
3 8 13
5 9 13
6 8 10 12 14
其中6 8 10 12 14最长,长度为5。
现在给出N个数,你来从中找出一个长度 >= 200 的等差数列,如果没有,输出No Solution,如果存在多个,输出最长的那个的长度。
Input
第1行:N,N为正整数的数量(1000 <= N <= 50000)。 第2 - N+1行:N个正整数。(2<= A[i] <= 10^9) (注,真实数据中N >= 1000,输入范例并不符合这个条件,只是一个输入格式的描述)
Output
找出一个长度 >= 200 的等差数列,如果没有,输出No Solution,如果存在多个,输出最长的那个的长度。
Input示例
10 1 3 5 6 8 9 10 12 13 14
Output示例
No Solution
提供一个可能是正解的做法吧(源自本题讨论区),也就是@李陶冶 提到的分治做法,来自Jeff Erickson在1999年发表的一篇短论文 Finding longest arithmetic progressions 。
这个论文的做法基于一个结论:大小为n的集合里长度至少为k的不同等差数列的数量是O(n*n/k*k)的。从这个结论入手可以尝试构造出答案。
首先要明确等差数列的定义。
一个等差数列可以用首项、公差和项数的三元组(first,delta,length)表示,当然这个first也可以换成末项last,取决于你的算法。
我们要考虑的等差数列要尽量用少的信息表示所有的可能,也就是说提取出来的等差数列之间不能有相互包含的关系,例如(2,3,4)和(2,3,5)就冗余了,而(2,3,4)和(5,3,4)也是冗余的,因为当它们出现在同一个序列里时,(2,3,5)也是存在的。
因此我们只需要考虑满足(first-delta)这个数字不存在,并且(last+delta)这个数字不存在的等差数列。
接下来要发现这样的等差数列在k充分大时是不多的。
定义集合里的两个元素是足够相邻的,当且仅当它们的排名之差不超过n/(k-1)。由鸽巢原理可知,一个长度为k的等差数列必然包含至少(k-1)/2对足够相邻的元素(考虑等差数列里排名最小的和排名最大的那两个元素)。
而集合里足够相邻的元素对数不超过n*n/2(k-1),我们所求的不同等差数列也不会包含相同的一对足够相邻的元素,所以我们能得到的等差数列数量是不超过n*n/(k-1)*(k-1)的。
最后要利用这个性质来构造相应的算法。
利用数量不多的性质,我们可以尝试找出所有长度至少为 200 的等差数列,从中选取最长的那个。
注意到上面的证明利用到了集合的大小,类似地也可以证明,这样的等差数列必然有⌊k/2⌋项在前⌊n/2⌋小的元素集合里,或者有⌊k/2⌋项在前⌈n/2⌉大的元素集合里。而集合大小减半同时数列项数减半对数列数量的影响是不大的,所以可以尝试对集合分治,分别找出更小情况的解,然后放到大的集合里尝试向左向右O(k)扩张,再进行去重,从而得到当前情况的解。
不妨设T(n,k)表示从大小为n的集合里提取长度至少为k的不同等差数列的复杂度,则有T(n,k)=2T(n/2,k/2)+O((n*n/k*k)k)⇒T(n,k)=O(n*n/k
log n/k)。
实现上可能要注意几个细节:
1. 需要实现原集合的hash来保证扩张的复杂度。
2. 当k=O(log n)时,可以采用O(n*n)的dp直接计算,效率更高,此时递归层数是O(log k)的。
3. k实际上可以动态变化,从而优化常数,本题数据好像不太强,稍微写错点地方也不会出问题。
4. 论文中提到寻找最长等差数列的时候依次选取k=n,n/2,n/4,…,对于本题可以直接选取k=200。
———————————————————————————————————————————————
但因为种种原因,本题可以玄学搜索+各种剪枝用O(n*n)的时间复杂度完成。搜索方法:枚举第一个数,然后枚举公差,用hash判断可行性。
Code:
uses math; const m=10000000; var num,k,i,n,minans,maxans,len,ans,j,d:longint; a,hash:array[0..10000005] of longint;t:int64; procedure sort(l,r:longint); var i,j,x,y:longint; begin i:=l;j:=r;x:=a[(l+r) div 2]; repeat while a[i]<x do inc(i); while x<a[j] do dec(j); if not(i>j) then begin y:=a[i];a[i]:=a[j];a[j]:=y; inc(i);dec(j); end; until i>j; if l<j then sort(l,j); if i<r then sort(i,r); end; function hashhash(x:longint):longint; var k:longint; begin k:=x mod m; if k=0 then k:=k+m; while (hash[k]<>0)and(hash[k]<>x) do k:=k mod m+1; exit(k); end; function find(x:longint):boolean; begin if hash[hashhash(x)]=x then exit(true); exit(false); end; begin read(n); minans:=maxlongint; for i:=1 to n do begin read(a[i]); if a[i]>maxans then maxans:=a[i]; if a[i]<minans then minans:=a[i]; end; sort(1,n); a[0]:=a[1]-1; for i:=1 to n do hash[hashhash(a[i])]:=a[i]; for i:=1 to n do for j:=i+1 to n do begin d:=a[j]-a[i]; k:=a[j];len:=2; t:=a[i]+ans*d; if (t>maxans)or(t<minans) then break; while find(k+d) do begin k:=k+d; inc(len); end; if len>ans then ans:=len; end; if ans<200 then writeln('No Solution') else writeln(ans); end.
相关文章推荐
- [51Nod](1089)最长回文子串 V2 ---- Manacher算法
- 1056 最长等差数列 V2
- 51nod 1056 最长等差数列 V2
- 【51nod 1055 & 1056】最长等差数列及V2 题解
- 51 nod 1055 最长等差数列(dp)
- 51 nod 1055 最长等差数列(DP)
- [51nod 1056]最长等差数列V2
- 51nod 1056 最长等差数列 V2
- 51 nod 1188 最大公约数之和 V2(狄利克雷卷积+线性筛法)
- 51 Nod 1130 N的阶乘的长度 V2(斯特林近似)
- 51 NOD 1022 石子归并 V2(dp四边形加速)
- 51 Nod 1086 背包问题 V2
- 背包问题 V2 (51Nod - 1086)
- 51 Nod 1028 大数乘法 V2【Java大数乱搞】
- 51 nod 1376 最长递增子序列的数量(二维偏序cdq)
- 51 nod 1130 N的阶乘的长度 V2(斯特林近似)
- 51 nod 1022 石子归并 V2(dp决策单调性)
- 51 nod 1200 石子游戏V2 FWT
- 【最长上升子序列】51 nod 最长单增子序列
- 51 NOD 1119 机器人走方格 V2(Lucas)