1085 Perfect Sequence (25 分)

题意

从N个正整数中选择若千个数,使得选出的这些数中的最大值不超过最小值的p倍。问满足条件的选择方案中,选出的数的最大个数。

思路

由于题干中涉及序列的最大值和最小值,因此不妨先将所有N个正整数从小到大进行排序。在此基础上证明:能使选出的数个数最大的方案,一定是在该递增序列中选择连续的若千个数的方案。

证明:
设递增序列A为\({a_1,a_2, \cdots ,a_i,a_{i+1}, \cdots ,a_{i+m}, \cdots ,a_j, \cdots ,a_n}\),假设从中能选出的个数最大的方案为\({a_i,a_{i+1}, \cdots ,a_{i+m},a_j}\),即\(a_i,a_{i+1}, \cdots ,a_{i+m}\)为序列中连续的数,\(a_j\)为与其在序列中不连续的数。因此该方案中的最大值为\(a_j\),最小值为\(a_i\),且满足\(a_j \le a_i * p\)。而事实上,由于原序列A中元素是递增的,因此可以把方案扩充为连续序列\(a_i,a_{i+1},\cdots,a_j\),而这个序列的最大值仍然为\(a_j\),最小值仍然为\(a_i\),因此\(a_j \le a_i * p\)仍然成立。于是就找到了一个新的序列,使得它的长度比原先不连续序列的长度要长。因此假设不成立,得出结论:能使选出的数的个数最大的方案,一定是在该递增序列中选择连续的若千个数的方案。证毕。

于是问题就转换成:在一个给定的递增序列中,确定一个左端点a[i]和一个右端点a[j],使得\(a[j] \le a[i] * p\)成立,且j-i最大。

如果强制进行\(O(n^2)\)的二重循环枚举,那么根据题目的数据范围,肯定是会超时的。这里有两种方法来解决这个问题:二分查找和two pointers。

从左至右扫描序列,对其中的每一个数a[i],在a[i+1] ~ a[n-1]内二分查找第一个超过a[i] * p的数的位置j,这样j-i就是对位置i来说满足\(a[j] \le a[i] * p\)的最远长度。取所有j-i的最大值即为所求的答案,时间复杂度为\(O(logn)\)

代码

二分:

const int N=1e5+10;
int a[N];
int n,p;

int main()
{
    cin>>n>>p;

    for(int i=0;i<n;i++) cin>>a[i];
    
    sort(a,a+n);

    int ans=0;
    for(int i=0;i<n;i++)
    {
        int j=upper_bound(a+i,a+n,(LL)a[i]*p)-a;
        ans=max(ans,j-i);
    }

    cout<<ans<<endl;

    //system("pause");
    return 0;
}

首先,可以很容易地获得下面这个性质:如果a[j] ≤ a[i] * p成立,那么对[i, j]内的任意位置k,一定有a[k] ≤ a[i] * p也成立。这种有序序列的性质就引导我们往two pointers思想去考虑,由此可以得到以下时间复杂度为\(O(n)\)的算法。

令两个下标i、j的初值均为0,表示i、j均指向有序序列的第一一个元素,并设置计数器count存放满足a[j] ≤ a[i] * p的最大长度。接下来让j不断增加,直到不等式a[j] ≤ a[i] * p恰好不成立为止(在此过程中更新count)。之后让下标i右移一位,并继续上面让j不断增加的操作,以此类推,直到j到达序列末端。这个操作的目的在于,在a[j] ≤ a[i] * p的条件下始终控制i和j的距离最大。

双指针:

const int N=1e5+10;
int a[N];
int n,p;

int main()
{
    cin>>n>>p;

    for(int i=0;i<n;i++) cin>>a[i];
    
    sort(a,a+n);

    int ans=0;
    int j=0;
    for(int i=0;i<n;i++)
    {
        while(j<n && a[j] <= (LL)a[i]*p) j++;
        ans=max(ans,j-i);
    }

    cout<<ans<<endl;

    //system("pause");
    return 0;
}
posted @ 2021-02-19 13:30  Dazzling!  阅读(48)  评论(0编辑  收藏  举报