Acwing-109 天才ACM(倍增+归并)

天才ACM

给定一个整数 M,对于任意一个整数集合 S,定义“校验值”如下:

从集合 S 中取出 M 对数(即 2∗M个数,不能重复使用集合中的数,如果 S 中的整数不够 M 对,则取到不能取为止),使得“每对数的差的平方”之和最大,这个最大值就称为集合 SS 的“校验值”。

现在给定一个长度为 N 的数列 A 以及一个整数 T。

我们要把 A 分成若干段,使得每一段的“校验值”都不超过 T。

求最少需要分成几段。

输入格式

第一行输入整数 K,代表有 K 组测试数据。

对于每组测试数据,第一行包含三个整数 N,M,T 。

第二行包含 N 个整数,表示数列A1,A2…AN.

输出格式

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

数据范围

1 ≤ K ≤ 12 1\leq K\leq 12 1K12
1 ≤ N , M ≤ 500000 1\leq N,M\leq 500000 1N,M500000
0 ≤ T ≤ 1 0 18 0\leq T\leq 10^{18} 0T1018
0 ≤ A i ≤ 2 20 0\leq A_i\leq 2^{20} 0Ai220

输入样例

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

输出样例

2
1
链接:

https://www.acwing.com/problem/content/description/111/

分析:

首先对于一个集合要使得每对数的差的平方之和最大,那么需要取 ( 最 大 值 − 最 小 值 ) 2 + ( 次 大 值 − 次 小 值 ) 2 + . . . 共 M 对 (最大值-最小值)^ {2} + (次大值-次小值)^2 + ...共M对 ()2+()2+...M
这样求的校验值最大。而让分成的段数尽量少,也就是让每一段尽量长,看最终能分几段。
于是,问题转化为了:确定了左端点,让你找到满足情况下(校验值<T)的最大的右端点。
求校验值我们需要排序配对,时间复杂度为O(N*logN)。当校验值上限T比较小时,二分就比较浪费时间。我们可以使用倍增+归并的思想来做这道题。
倍增:
1、初始化len = 1, L=R
2、求出[L,R+len]这一段的校验值,若校验值 ≤ T \leq T T,则R+=len, len *=2,否则len/=2;
3、重复上一步,直到len的值变为0,此时R即为所求
上面这个过程最多循环O(log(N))次,每次循环对长为O(R-L)的一段进行排序,我们可以不对整个区间进行排序,可以采用归并排序的方法,只对新增的后一半排序,前一半之前已对其排好序了,合并两段,复杂度会降到O(Nlog(N))。

代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn =  500000 +10;
int n, m;
ll t;
int p[maxn];
int a[maxn];
int b[maxn];
int ans = 0;
void merge(int l, int r, int mid)
{
    int pos = l;
    int ll = l;
    int rr = mid;
    while(ll < mid && rr <= r)
    {
        if(a[ll] < a[rr]){
            b[pos++] = a[ll++];
        }
        else {
            b[pos++] = a[rr++];
        }
    }
    while(ll < mid) {
        b[pos++] = a[ll++];
    }
    while(rr <= r) {
        b[pos++] = a[rr++];
    }
}
bool check(int l, int r, int mid)
{
    for(int i = mid; i <= r; i++) {
        a[i] = p[i];
    }
    sort(a+mid, a+r+1);
    merge(l, r, mid);
    ll sum = 0;
    for(int i = 1; i <= (r-l+1)>>1 && i <= m; i++) {
        sum += ((ll)(b[r-i+1] - b[l+i-1]) * (ll)((b[r-i+1] - b[l+i-1])));
    }
    if(sum <= t) {
        for(int i = l; i <= r; i++)
            a[i] = b[i];
        return true;
    } else
        return false;
}
void solve()
{
    int l, r;
    l = r = 1;
    int len = 1;
    a[l] = p[l];
    while(r <= n) {
        if(!len) {
            ans++;
            l = (++r);
            len = 1;
            a[l] = p[l];
        }
        else if(r + len <= n && check(l, r+len, r+1)){
            r = r + len;
            len <<= 1;
            if(r == n)  break;
        }
        else {
            len >>= 1;
        }
    }
    if(r == n)
        ans++;
}
int main()
{
    ios::sync_with_stdio(false);
    int k;
    cin >> k;
    while(k--) {
        cin >> n >> m >> t;
        for(int i = 1; i <= n; i++) {
            cin >> p[i];
        }
        ans = 0;
        solve();
        cout << ans << endl;
    }

    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值