题目描述
时间限制: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
小的元素作为最小值,第
设排序之后的列表为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+12k−i−1+1=2j−i
利用第二条,枚举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;
}