AcWing 天才ACM

AcWing 天才ACM

Description

  • 给定一个整数 ,对于任意一个整数集合 S从集合 S 中取出 对数(即 2∗M 中的整数不够 M 的“校验值”。现在给定一个长度为 N 的数列 以及一个整数 T我们要把 A 分成若干段,使得每一段的“校验值”都不超过 。求最少需要分成几段。

Input

  • 第一行输入整数 ,代表有 K对于每组测试数据,第一行包含三个整数 N,M,T 。第二行包含 N 个整数,表示数列。

Output

  • 对于每组测试数据,输出其答案,每个答案占一行。

Data Size

  • 1≤K≤12,
    1≤N,M≤500000,
    0≤T≤1018,
    0≤A_i≤220

Sample Input

2
5 1 49
8 2 1 7 9
5 1 64
8 2 1 7 9

Sample Output

2
1

题解:

  • 倍增 + 归并排序
  • 首先要让每对数的差的平方之和最大,不就是最大跟最小匹配,次大跟次小匹配吗?然后贪心的考虑,如果我分的段越多,那么每一段就越不容易超过T。那么题目要我求最少分多少段,也就是问每一段都要在<=T的前提下包含尽量多的数。
  • 所以每一段到底要包含多少个数呢?可以二分。
  • 二分然后对所求出的区间sort,找出“校验值”。<=T返回true,否则返回false
  • 但是可以告诉你,这样会被卡。
  • 因为sort的复杂度是O(nlogn),然后二分如果每次每一段的右端点都只向右边挪动了一点点,复杂度连暴力都不优… …这时候就拼RP了
  • 所以,要换一种更保险的方法
  • 可以用倍增。理论基础是任意数都可以被拆成多个2的x次方相加。倍增流程如下:
  1. 初始化r = l, p = 1
  2. 求出[l, r + p]这一段区间的“校验值”,若校验值<=T,则r += p, p *= 2,否则p /= 2
  3. 重复上一步,直到p值变为0,此时r为所求右边界
  • 倍增的好处主要是区间是从小到大逐渐变大的,这样旧的区间就不用反复的sort,而是只用sort后面新加进来的一小段区间,然后用归并排序的思想合并成一个大区间即可。
  • 复杂度大概为O(nlogn)
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#define N 500005
#define int long long
using namespace std;

int T, n, m, k;
int a[N], b[N], c[N], t[N];

int read()
{
    int x = 0; char c = getchar();
    while(c < '0' || c > '9') c = getchar();
    while(c >= '0' && c <= '9') {x = x * 10 + c - '0'; c = getchar();}
    return x;
}

bool merge(int l1, int r1, int l2, int r2)
{
    int cnt = 0, p1 = l1, p2 = l2, r = 0;
    while(p1 <= r1 && p2 <= r2)
    {
        if(c[p1] <= b[p2]) t[++cnt] = c[p1], p1++;
        else t[++cnt] = b[p2], p2++;
    }
    while(p1 <= r1) t[++cnt] = c[p1], p1++;
    while(p2 <= r2) t[++cnt] = b[p2], p2++;
    p1 = 1, p2 = cnt, cnt = 0;
    while(cnt < m && p1 < p2)
    {
        r +=  (t[p1] - t[p2]) * (t[p1] - t[p2]);
        p1++, p2--, cnt++;
    }
    if(r <= k)
    {
        cnt = 0;
        for(int i = l1; i <= r2; i++) c[i] = t[++cnt];
        return 1;
    }
    else return 0;
}

bool check(int l, int r1, int r2)
{
    for(int i = r1 + 1; i <= r2; i++) b[i] = a[i];
    sort(b + r1 + 1, b + 1 + r2);
    return merge(l, r1, r1 + 1, r2);
}

signed main() //因为宏定义用了骚皮操作,所以这里只能写signed
{
    cin >> T;
    while(T--)
    {
        n = read(), m = read(), k = read();
        for(int i = 1; i <= n; i++) a[i] = read();
        int ans = 0,l = 1, r, p;
        while(1)
        {
            c[l] = a[l], r = l, p = 1, ans++;
            while(p)
            {
                if(check(l, r, min(n, r + p))) r += p, p *= 2;
                else p /= 2;
                if(min(n, r) == n) break;
            }
            l = r + 1;
            if(min(n, r) == n) break;
        }
        printf("%lld\n", ans);
    } 
    return 0;
}

转载于:https://www.cnblogs.com/BigYellowDog/p/11278993.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值