题面
给定一个由 n 个不同的正整数组成的数组 a。
让我们考虑一个无限整数集合 S,它包含所有满足以下条件之一的整数 x:
x=ai 对于某些 1≤i≤n。
x=2y+1 且 y 在 S 中。
x=4y 并且 y 在 S 中。
例如,如果 a=[1,2],则 S 中的 10 个最小元素将是 {1,2,3,4,5,7,8,9,11,12}。
找出 S 中严格小于 2的p次方 的元素个数。 由于这个数字可能太大,所以以 109+7 为模打印。
输入
第一行包含两个整数 n 和 p (1≤n,p≤2⋅105)。
第二行包含 n 个整数 a1,a2,…,an (1≤ai≤109)。
保证 a 中的所有数字都是不同的。
输出
打印一个整数,即 S 中严格小于 2的p次方 的元素数。 请记住以 109+7 为模打印。
题解思路
由数据范围以及限制S的数 2的p次方 非常大 , 推断在二进制上做。
根据题目的两种操作,
操作1可以将一个数的二进制左移1位并在末尾添加1,
操作2可以将一个数的二进制左移2位。
操作只能从小到大,我们可以进行排序,尝试弄出无需操作的一些冗余元素(即一些由小的数操作出来的数,只需保留小的数就能伸缩出这个数的所有数)。
这样每个数操作后的数都是不同的了。考虑在进制上dp。
dp 最高位位 i 位的数的个数 ,
根据上面的操作,以及操作的数一定不同,很容易得出转移方程。
AC代码
#include <bits/stdc++.h>
//#include <unordered_map>
//priority_queue
#define PII pair<int,int>
#define ll long long
using namespace std;
const int INF = 0x3f3f3f3f;
const int N = 200100;
const int mod = 1e9 + 7 ;
long long a[N] ;
long long dp[N] ;
void solve()
{
int n , p ;
cin >> n >> p ;
map <long long , int > mp ;
for (int i = 1 ; i <= n ; i++ )
cin >> a[i] ;
sort(a+1,a+1+n) ;
vector <long long > sp ;
for (int i = 1 ; i <= n ; i++ )
{
long long sk = a[i] ;
if ( !mp[sk] )
{
int falg = 1 ;
while ( sk )
{
if ( sk % 4 == 0 )
{
if ( mp[sk/4] )
{
falg = 0 ;
break ;
}else
sk/=4 ;
}else if ( sk% 2 )
{
if ( mp[sk/2] )
{
falg = 0 ;
break ;
}else
sk/=2 ;
}else
break ;
}
mp[a[i]] = 1 ;
if ( falg )
sp.push_back(a[i]) ;
}
}
for ( int i = 0 ; i < sp.size() ; i++ )
{
int fw = 0 ;
for (int j = 0 ; j < 32 ; j++ )
{
if ( sp[i] >> j & 1 )
fw = j ;
}
dp[fw]++;
}
for (int i = 0 ; i < p ; i++ )
{
dp[i+1] = ( dp[i] + dp[i+1] )%mod ;
dp[i+2] = ( dp[i] + dp[i+2] )%mod ;
}
long long ans = 0 ;
for (int i = 0 ; i <= p-1 ; i++ )
ans = ( ans + dp[i] ) %mod ;
cout << ans << "\n" ;
}
int main()
{
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
solve() ;
return 0 ;
}