51nod 1056 最长等差数列 V2

42 篇文章 0 订阅
这个和1055那个题差不多,稍微改改就过了,首先是剪枝的改写,这个剪枝是必过的,唐老师在讨论里进行了证明。
首先要明确等差数列的定义。
一个等差数列可以用首项、公差和项数的三元组  (first,delta,length)  表示,当然这个  first  也可以换成末项  last  ,取决于你的算法。
我们要考虑的等差数列要尽量用少的信息表示所有的可能,也就是说提取出来的等差数列之间不能有相互包含的关系,例如  (2,3,4)  和  (2,3,5)  就冗余了,而  (2,3,4)  和  (5,3,4)  也是冗余的,因为当它们出现在同一个序列里时,  (2,3,5)  也是存在的。
因此我们只需要考虑满足  (firstdelta)  这个数字不存在,并且  (last+delta)  这个数字不存在的等差数列。

接下来要发现这样的等差数列在  k  充分大时是不多的。
定义:集合里的两个元素是足够相邻的,当且仅当它们的排名之差不超过  nk1  。由鸽巢原理可知,一个长度为  k  的等差数列必然包含至少  k12  对足够相邻的元素(考虑等差数列里排名最小的和排名最大的那两个元素)。
而集合里足够相邻的元素对数不超过  n22(k1)  ,我们所求的不同等差数列也不会包含相同的一对足够相邻的元素,所以我们能得到的等差数列数量是不超过  n2(k1)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;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值