[hihocoder1546]集合计数

题目描述

时间限制:10000ms
单点时限:1000ms
内存限制:256MB
描述
给定一个包含N个整数的集合S={A1, A2, … AN},以及一个给定的整数K,请计算有多少个S的子集满足其中的最大值与最小值的和小于等于K。

例如对于S={4, 2, 5, 8}以及K=7,满足的条件的子集有以下4个:{2}, {2, 4}, {2, 5}, {2, 4, 5}。

输入
第一行包含两个整数N和K。

第二行包含N个整数A1, A2, … AN。

对于30%的数据,1 <= N <= 20

对于70%的数据,1 <= N <= 1000

对于100%的数据,1 <= N <= 100000, 0 <= Ai <= 1000000000, 0 <= K <= 2000000000, A1 ~ AN 两两不同。

输出
输出满足条件的子集数目。由于答案可能非常大,你只需要输出答案除以1000000007的余数。

样例输入
4 7
4 2 5 8
样例输出
4

算法简介

先确认题面,笔者错读了几个地方,导致一直错解:1. 是最大值和最小值的和,这句话本来被笔者理解为了集合的和,修正之后又忘记了是两个的和(对于集合内个数为1的情况,也要加两次) 2. 对于和取模。然后开始解题:
最大值和最小值的和小于某个给定值的子集数目,首先肯定想到要排序。这里笔者直接采用了algorithm里面的sort函数,复杂度应该为 O(NlogN)

对于排序之后的序列,最简单的方法就是逐一寻找最大值和最小值对,然后求取以两者为最大小值的子集个数:采用第 n 小的元素作为最小值,第m小的元素作为最大值,则以两者为最大小值的子集个数为 2mn1 (最大小值必定位于集合内,大小位于两者之间的元素有 mn1 个,每个元素都有两种可能性,出现/不出现),然后逐一比较任意两个元素,复杂度为 C2N+O(N)=O(N2) 。注意到计算量在于需要逐一比较任意两个元素,需要加以缩减:
设排序之后的列表为arr,从小到大排序
1. 若arr[i]+arr[j] <= K,则有arr[i]+arr[k] <= K, for k < j && k >= i
2. 若arr[i]*2 > K,则有arr[i] + arr[j] > K, for any j > i
3. 若arr[i]+arr[j+1] > K,则有arr[i+1]+arr[j+1] > K
利用第一条,对于任意最小值arr[i],只需要找到满足arr[i]+arr[j]<=K中的最大值即可,对于任意k < j && k >= i,arr[k]和arr[i]都可以作为最大小值对。而所有子集的个数为 jk=i+12ki1+1=2ji
利用第二条,枚举arr[i]的时候,从小到大,一旦遇到2*arr[i] > K的情况,即可停止枚举。
利用第三条,最小值从小到大的枚举过程中,最大值的枚举也是从大到小,不需要每次都重新搜索arr[j],只需要从arr[i-1]的arr[j]继续向小枚举即可。
利用上诉三条,最终使得复杂度减为 O(N)

对于 2n 的计算,因为需要多次使用,所以使用动态规划,缩减复杂度,最终最多需要计算N次计算即可,即复杂度为 O(N)

代码

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

#define MOD 1000000007

int N,K;
vector<int> arr(100002);
int exp_2[100002];

int exp2(int i) {
    if (exp_2[i] == 0)
        exp_2[i] = (2*exp2(i-1))%MOD;
    return exp_2[i];
}

int main()
{
    exp_2[0] = 1;

    cin >> N >> K;
    for (int i = 0;i < N;++i)
        cin >> arr[i];
    sort(arr.begin(),arr.begin()+N);

//    for (int i = 0;i < N;++i)
//        cout << arr[i] << ' ';
//    cout << endl;

    int rear = N-1;
    int result = 0;
    for (int i = 0;i < N && 2 * arr[i] <= K;++i) {
        result = (result + 1) % MOD;
        for (;rear >= i && arr[i]+arr[rear] > K;--rear);
        if (rear >= i)
            result = (result+exp2(rear-i)-1)%MOD;
    }
    cout << result << endl;

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值