zoukankan      html  css  js  c++  java
  • acwing109

    参考acwing https://www.acwing.com/solution/content/15458/ 垫底抽风dalao的题解

    又是一道神题

    暴力二分写法(n^2logn),非常暴力好想

    int n, m;
    int ans;              // 存答案
    ll T;                 // 题目中的 T
    ll w[N], t[N];        // w 是输入的数组,t 是用于求校验值的数组
    ll sq(ll x){return x * x;}           // 返回 x 的平方{
    ll get(int l, int r){  // 求原数组区间 [l, r] 的校验值
        int k = 0;        // 要先把 w 的 [l, r] 这段复制到 t 中,用 k 记录 t 的长度。
        for (int i = l; i <= r; i ++ ) // 从 l 到 r 枚举一遍,将 w 数组复制到 t 数组中
            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]); // 双指针,i 指向当前集合中剩余的最小数,k 指向当前集合中剩余的最大数
        return sum;
    }
    int main(){
            cin >> n >> m >> T;
            for (int i = 0; i < n; i ++ ) cin >> w[i];
            ans = 0;      // 答案归零
            int start = 0;    // start 记录当前剩余的区间左端点
            while (start < n){ // start < n 说明当前数组还有值,需要继续划分。结束时 start 应等于 n 
                int l = start, r = n, mid; // 二分求出当前能划分的最长的区间
                while (l < r){ // 二分板子
                    mid = l + r >> 1;
                    if (get(start, mid) > T) r = mid;
                    else    l = mid + 1;
                }
                start = r;    // 二分完后,r 即当前可划分的最长区间的下一个位置,将 start 制为 r。
                ans ++ ;      // 每次划分完一个区间,ans ++ 
            }
            printf("%d
    ", ans);
    }
    
    

    倍增想法(nlog^2n) 可过但是有更好更快的做法

    		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);
        }
    

    倍增+归并(nlogn) 跑的飞起

    bool check(int l, int mid, int r)           // 判断区间 [l, r) 是否合法,并将 t 中的 [l, mid) 区间和 [mid, r) 区间合并到 tmp 中
    {
        for (int i = mid; i < r; i ++ )         // 将 w 数组的 [l, r) 区间复制到 t 的 [l, r) 区间中
            t[i] = w[i];
        sort(t + mid, t + r);                   // 将 t 的 [mid, r) 排序
        int i = l, j = mid, k = 0;              // 双指针进行区间合并
        while (i != mid && j != r)              // 当 i 不到 mid 且 j 不到 r 时,执行循环
            if (t[i] < t[j])                    // 如果 t[i] 比 t[j] 小,那么将 t[i] 放入 tmp 中
                tmp[k ++ ] = t[i ++ ]; 
            else                                // 否则将 t[j] 放入 tmp 中
                tmp[k ++ ] = t[j ++ ];
        while (i != mid) tmp[k ++ ] = t[i ++ ]; // 处理剩下的元素
        while (j != r) tmp[k ++ ] = t[j ++ ];
        ll sum = 0;                             // 计算校验值
        for (i = 0; i < m && i < k; i ++ , k -- )
            sum += sq(tmp[i] - tmp[k - 1]);
        return sum <= T;                        // 返回当前区间 [l, r) 是否合法
    }
    
    int main()
    {
        int K;
        scanf("%d", &K);
        while (K -- )
        {
            scanf("%d %d %lld
    ", &n, &m, &T);
            for (int i = 0; i < n; i ++ )
                scanf("%lld", &w[i]);
            ans = 0;
            int len;
            int start = 0, end = 0;
            while (end < n)
            {
                len = 1;
                while (len)
                {
                    if (end + len <= n && check(start, end, end + len)) // 如果 w 的 [start, end + len) 区间合法
                    {
                        end += len, len <<= 1;
                        if (end >= n) break ;               // 一个小剪枝,如果 end >= n,那么直接跳出
                        for (int i = start; i < end; i ++ ) // 在 check 时,已经将 t 数组的 [start, end + len) 这段区间归并在 tmp 中了。现在只需要将 tmp 中的有序数组复制到 t 中即可
                            t[i] = tmp[i - start];          // 复制的时候注意下标变换,tmp 是从 0 开始存的,t 是从 start 开始存的
                    }
                    else    len >>= 1;
                }
                start = end;
                ans ++ ;
            }
            printf("%d
    ", ans);
        }
        return 0;
    }
    
  • 相关阅读:
    VS操作Sqlite数据库
    Sqlite官方下载对应版本注意细节
    样式
    移动端问题
    table最终版IE(浏览器中宽度不起作用)
    pointer-event:none;在IE浏览器中不起作用
    伪类的使用
    针对谷歌浏览器写的css样式
    IE浏览器中textarea有滚动条出现
    父级元素被子元素的margin-top值影响的解决方案
  • 原文地址:https://www.cnblogs.com/shikeyu/p/13741317.html
Copyright © 2011-2022 走看看