zoukankan      html  css  js  c++  java
  • 109.天才ACM

    原题链接:109. 天才ACM


    解题思路

    首先,对于一个集合S,显然应该取S中最大的M个数和最小的M个数,最大和最小构成一对,次大和次小构成一对···这样求出“校验值”最大。而为了让数列A分成的段数最少,每一段都应该在“校验值”不超过T的前提下,尽量包含更多的数。所以我们从头开始对A进行分段,让每一段尽量长,到达结尾时分成的段数就是答案。

    于是,需要解决的问题为:当确定一个左端点L之后,右端点R在满足A[L]~A[R]的“校验值”不超过T的前提下,最大能取到多少。

    求长度为N的一段“校验值”需要配对,时间复杂度为O(NlogN)。当“校验值”上限T比较小时,如果在整个L~N的区间上二分右端点R,二分的第一步就要检验(N-L)/2这么长一段,最终右端点R却可能只扩展了一点,浪费了很多时间。与上一道题目一样,我们需要一个与右端点R扩展的长度相适应的算法----倍增。

    可以采用与上一题类似的倍增过程:
    1.初始化 p=1,R=L。
    2.求出[L,R+p]这一段区间内的“校验值”<=T,则R+=p,p*=2,否则p/=2.
    3.重复上一步,直到p的值变为0,此时R即为所求。

    样例代码

    #include <iostream>
    #include <algorithm>
    
    using namespace std;
    
    typedef long long ll;
    
    const int N = 500005;
    
    int n, m;
    int ans;
    ll T;
    ll w[N], t[N];
    
    ll sq(ll x)
    {
        return x * x;
    }
    
    ll get(int l, int r)  // 计算原数组左闭右开区间 [l, r) 的校验值
    {
        int k = 0;
        for (int i = l; i < r; i ++ )
            t[k ++ ] = w[i];
        sort(t, t + k);
        ll sum = 0;
        for (int i = 0; i < m && i < k; i ++ , k -- )
            sum += sq(t[i] - t[k - 1]);
        return sum;
    }
    
    int main()
    {
        int K;
        scanf("%d", &K);
        while (K -- )
        {
            scanf("%d %d %lld
    ", &n, &m, &T); // 不用 scanf 的话,这么做有一个点会 TLE
            for (int i = 0; i < n; i ++ )
                scanf("%lld", &w[i]);
            ans = 0;
            int start = 0, end = 0;            // start 记录剩余区间开头节点,end 记录当前考虑区间的尾结点(左闭右开)
            while (end < n)
            {
                int len = 1;                   // len 初始化为 1
                while (len)                    // len 为 0 自动跳出
                {
                    if (end + len <= n && get(start, end + len) <= T) // 如果说 len + end 还在 n 以内,且区间 [start, end + len) 的校验值不大于 T
                        end += len, len <<= 1; // 那么 end += len,len *= 2
                    else    len >>= 1;         // 否则 len /= 2
                }
                start = end;                   // 让 start 指向当前区间末尾结点的下一个位置,由于区间是左闭右开的,所以直接指向 end 就可以了
                ans ++ ;                       // 每次循环都找到了一个区间,所以让 ans ++ 
            }
            printf("%d
    ", ans);
        }
        return 0;
    }
    // 偷个懒,少写点注释 (  ̄▽ ̄)σ
    
  • 相关阅读:
    CodeForces-786B Legacy (线段树优化建图,单源最短路)
    CodeForces-528C Data Center Drama
    CodeForces-723E One-Way Reform
    2-SAT入门
    POJ-3683 Priest John's Busiest Day (2-SAT 求任意可行方案)
    转载: 8天学通MongoDB——第一天 基础入门
    C# 非EF注册登录与EF注册登录
    Asp.Net入门(三)
    非EF分页
    sql语句错误大集合
  • 原文地址:https://www.cnblogs.com/hnkjdx-ssf/p/14278899.html
Copyright © 2011-2022 走看看