这个题想了我超级久,最后还想错了,多亏有大佬指点才得以理清思路
首先是要求集合,并且题目也提示了没有相同元素,所以可以先排个序,这样我们就可以将这个排序后的数组当成是一个集合,然后去求满足
条件的小集合。所以排序可以帮我们获取最大值和最小值。
这时我们可以设两个变量表示数组的下标,一个是low,一个是high,因为获取小集合我们进行的操作就是从大集合中挑元素出去
由于N<=100000,所以N平方的做法是要超时的(我一开始就是超时),但是打表是完全木有问题的,所以我们将表示i个元素有x
种集合的表打出来,i表示元素个数,即数组下标,x代表这么多个元素可以形成的集合的个数。
为什么可以打这样一个表呢,因为只要一个集合的最大值和最小值满足条件,那么去掉这个最大值后的子集也是满足条件的,他们中的元素有包含和不包含两种情况
所以每次都是乘2
这样我们就得到了一个映射关系,但刚才那个逻辑推过来是要超时的,比如2 4 5 8,2和8中间有两个元素,所以可以产生4种集合,然而这样你就还必须
求2,4,5和4,5,8所能产生的集合数,这样写下来是要用两层循环的,所以会超时。(我就是在这里卡了好久)
但是后来我发现,2 4 5 8的那两个使得计算超时的子集都包含4 5两个元素,所以我要设法使得一次计算可以去掉两边的元素
这时我发现2 4 5 8的子集的数量=2 4 5+4 5 8+4 5(这个式子只是用于理解,正确性估计是没有的hhh)
所以我可以得到 2 4 5 8的集合数等于F[2 8 之间的元素个数+1]-1,有了这个式子,复杂度就只有O(N)了
代码
多想想,草稿纸上多写几个,就会理解了
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
long long num[100005];
long long F[100005];
void f()
{
F[0]=1;
for(int i=1;i<100003;i++)
{
F[i]=F[i-1]*2;
F[i]%=1000000007;
}
}
int main()
{
int N;
f();
long long K;
while(~scanf("%d%lld",&N,&K))
{
long long ans=0;
for(int i=1;i<=N;i++)
{
scanf("%lld",&num[i]);
if(num[i]*2<=K)
ans++; //提前算掉这些一个点的集合个数,以后的集合就可以保证有两端了
}
sort(num+1,num+N+1);
int low=1,high=N;
while(low<=N) //每次都要移动low和high,low
{
while(num[high]+num[low]>K&&high>=low) //每次都滤掉那些数值过大(相对于现在的low来说)的元素
{
high--;
}
if(high>=low) //然后就计算
{
ans+=F[high-low]-1;
ans%=1000000007;
}
low++;
}
printf("%lld\n",ans);
}
return 0;
}