[USACO16OPEN]钻石收藏家Diamond Collector

由于相差不超过k才可以放在一起,要判断不超过k这个条件,显然我们需要排序

首先我们需要一个f数组,f[i]意义看代码开头注释,

假设我们可以选择的某一个区间是a[l]~a[r](已排序且最优(最长的意思)),那么这个区间要符合这样一个性质:

a[r]-a[l]<=k

移项一下,

a[r]<=k+a[l](r要尽可能大)

而a[l]中的l我们是枚举得到的,因此我们只需要求出对应的r即可

我们观察移项后的式子可以发现,我们要求的r其实是数列中小于等于k+a[l]的最大值

那么这显然是符合可二分性的,

所以我们二分查找r的位置,由此可以得到f[l]=half(k+a[l]) - l + 1;

然后我们可以枚举第一个架子放的是从哪里开始的

因为不允许重叠,因此我们需要查询f[f[i]+i]~f[n]的最大值(第二个架子),

为什么不用查询前面的?

与枚举数对同理,如果足够优的话,前面的会查询到后面的,所以不必重复查询(而且也不好处理,因为不知道前面查询到的会不会和后面冲突),

那么区间查询最大值,无修改,我们可以想到什么呢?

ST表!

那么ST表是什么?

ST[i][j]存从i开始往后面2^j个(包括i自己)的最大值

根据倍增的思想,我们可以在nlogn的时间内建出ST表

然后再次根据倍增的思想,我们可以实现O(1)查询

这里ST表就不作过多解释了,还不会的去写模板ST表即可

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 #define R register int
 4 #define AC 50020
 5 /*二分查找f[i],以i为开头最多能放几个,然后对f数组建立ST表,
 6 查询f[f[i]+i] ~ f[n]的最大值,
 7 为什么不用查询前面的?
 8 与枚举数对同理,如果足够优的话,前面的会查询到后面的,所以不必重复查询(而且也不好处理),
 9 这样的话,时间复杂度O(nlogn(获取f数组) + nlogn(获取ST表) + n(查询))
10 O(nlogn+n)? --- > O(nlogn)*/
11 int n,ans,k;
12 int ST[AC][17],f[AC],a[AC],p[AC];
13 inline int read()
14 {
15     int x=0;char c=getchar();
16     while(c>'9' || c<'0') c=getchar();
17     while(c>='0' && c<='9') x=x*10+c-'0',c=getchar();
18     return x;
19 }
20 
21 inline void upmax(int &a,int b)
22 {
23     if(b>a) a=b;
24 }
25 
26 inline int Min(int a,int b)
27 {
28     if(a<b) return a;
29     else return b;
30 }
31 
32 inline int Max(int a,int b)
33 {
34     if(a>b) return a;
35     else return b;
36 }
37 
38 inline int half(int x)//二分查找小于等于x的最大值,并返回下标
39 {
40     int l=1,r=n,mid;
41     while(l<r)
42     {
43         mid=(l+r+1)>>1;//由于符合条件时应尽可能向右移,这时为了保持ans在区间内,
44         //---> l=mid,但这样的话,由于传统mid偏向l,将会导致死循环
45         //因此将mid手动偏向r,这时由于不符合才会移动r,所以--->r=mid-1,本就不需要mid,
46         //因此每次转移必定会有偏移量,所以就不会死循环了
47         if(a[mid] <= x) l=mid;//由于mid偏向l,所以+1防止死循环
48         else r=mid-1;
49     }
50     return l;
51 }
52 
53 void pre()
54 {
55     n=read(),k=read();
56     for(R i=1;i<=n;i++) a[i]=read();
57     sort(a+1,a+n+1);
58     for(R i=1;i<=n;i++) f[i]=half(k+a[i]) - i + 1;//f[i]存个数
59 }
60 
61 void built()
62 {
63     int key=1;
64     for(R i=1;i<=n;i++)
65     {
66         if(i==(key<<1)) p[i]=p[i-1]+1,key<<=1;
67         else p[i]=p[i-1];//存下长度为i时,最长包含2的几次方,以保证一定包含了整个区间
68         ST[i][0]=f[i];
69     }
70     for(R j=1;j<=17;j++)
71         for(R i=1;i<=n;i++)
72             ST[i][j]=Max(ST[i][j-1] , ST[Min(i + (1 << (j - 1)),n)][j-1]);//因为可能剩下的i~n,根本就不够1<<j,所以要取Min
73 }
74 
75 void work()
76 {
77     for(R i=1;i<=n;i++)
78     {
79         int l=f[i] + i,r=n;
80         k=p[r-l+1];
81         upmax(ans,f[i] + Max(ST[l][k],ST[r - (1<<k) +1][k]));
82     }   
83     printf("%d\n",ans);
84 }
85 
86 int main()
87 {
88     freopen("in.in","r",stdin);
89     pre();
90     built();
91     work();
92     fclose(stdin);
93     return 0;
94 }

 

转载于:https://www.cnblogs.com/ww3113306/p/8762976.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值