这个和1055那个题差不多,稍微改改就过了,首先是剪枝的改写,这个剪枝是必过的,唐老师在讨论里进行了证明。
首先要明确等差数列的定义。
一个等差数列可以用首项、公差和项数的三元组
(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
充分大时是不多的。
定义:集合里的两个元素是足够相邻的,当且仅当它们的排名之差不超过
nk−1
。由鸽巢原理可知,一个长度为
k
的等差数列必然包含至少
k−12
对足够相邻的元素(考虑等差数列里排名最小的和排名最大的那两个元素)。
而集合里足够相邻的元素对数不超过
n22(k−1)
,我们所求的不同等差数列也不会包含相同的一对足够相邻的元素,所以我们能得到的等差数列数量是不超过
n2(k−1)2
的。
最后要利用这个性质来构造相应的算法。
利用数量不多的性质,我们可以尝试找出所有长度至少为 200 的等差数列,从中选取最长的那个。
注意到上面的证明利用到了集合的大小,类似地也可以证明,这样的等差数列必然有
⌊k2⌋
项在前
⌊n2⌋
小的元素集合里,或者有
⌊k2⌋
项在前
⌈n2⌉
大的元素集合里。
而集合大小减半同时数列项数减半对数列数量的影响是不大的,所以可以尝试对集合分治,分别找出更小情况的解,然后放到大的集合里尝试向左向右
O(k)
扩张,再进行去重,从而得到当前情况的解。
不妨设
T(n,k)
表示从大小为
n
的集合里提取长度至少为
k
的不同等差数列的复杂度,则有
T(n,k)=2T(n2,k2)+O(n2k2k)⇒T(n,k)=O(n2klognk)
。
其次,就是hash这个hash要手打,hash就是把之前的dp数组给换掉。暴力的去查找。最后,我想说的是,花了130个点头盾,以为能上第一,谁知道上了第二。代码还是发了吧。。。。
#include<cstdio>
#include<algorithm>
#define ll long long
using namespace std;
const int mod=233333;
int a[50010],ha[mod],ans=199,last[50010],n;
int read(){
char c=getchar();int k=0;for (;c<48||c>57;c=getchar());
for (;c>47&&c<58;c=getchar()) k=(k<<3)+(k<<1)+c-48;return k;
}
void add(int k,int i){
int x=k%mod;last[i]=ha[x];ha[x]=i;
}
bool find1(int k){
if (k>a[n]) return 0;
for (int i=ha[k%mod];i;i=last[i])
if (a[i]==k) return 1;
return 0;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
a[i]=read();
sort(a+1,a+n+1);
for(int i=1;i<=n;i++)add(a[i],i);
for(int i=2;i<=n-ans;i++)
{
int j=i-1,k=i+1;
while(j>0&&k<=n)
{
if(a[j]+a[k]>2*a[i])j--;
else if(a[j]+a[k]<2*a[i])k++;
else
{
ll d=a[i]-a[j];
ll maxn=d*(ll)ans+(ll)a[j];
if(maxn>a[n])break;
int now=3,now1=a[k]+d;
for(;find1(now1);now1+=d)now++;
ans=max(ans,now);
j--;
k++;
}
}
}
//cout<<ans<<endl;
if(ans>=200)
printf("%d\n",ans);
else printf("No Solution\n");
return 0;
}