Infinite Set(Codeforces Round #772(Div. 2))
题目大意
给你一个长度为\(n\)的每个元素都不同的数组\(a\)。现在规定一个无限集合\(S\),它包含元素\(x\)当且仅当:\(1、\)\(\forall i\in[1,n],x=a_i\);\(2、\)\(\forall y\in S,x=2y+1\);\(3、\)\(\forall y\in S,x=4y\)。给你一个数\(p\),问这个无限集合中有多少个元素严格小于\(2^p\),答案对\(1e9+7\)取模。\((1\leq n,p\leq2*10^5,1\leq a_i\leq10^9)\)
思路
这题一眼看还以为是一道容斥题,但是发现如果硬用容斥会很麻烦,于是就只能换个方向。首先我们可以把条件\(2\)稍微转换一下
\[\begin{aligned} x&=2y+1\\ x+1&=2(y+1)\\ \end{aligned} \]于是我们可以发现每次进行一次操作,\(\lfloor\log_2^x\rfloor\)的大小都会加\(1\)或\(2\),那么我们就能用\(dp\)来做这题。我们可以定义\(dp[i]\)为集合中大于等于\(2^i\)且小于\(2^{i+1}\)的数的个数,然后考虑转移,\(dp[i]+=dp[i-1]+dp[i-2]\),然后这道题差不多就做完了,最后只要再计算一下\(\sum_{i=0}^{p-1}dp[i]\)的值即可。
注:第一次计算个数的时候,有可能这个数本身就可以通过数列中某个数转化过来,于是我们可以稍微操作一下来消除这个问题带来的影响,具体见代码和注释。
代码
#include<bits/stdc++.h>
using namespace std;
long long mod=1000000007;
int a[200005];
set<int>vis;
long long dp[200005];
int main()
{
int n,p;
scanf("%d%d",&n,&p);
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
sort(a+1,a+n+1);
for(int i=1;i<=n;i++)
{
int x=a[i];
bool ck=1;
while(x)
{
if(vis.count(x)){ck=0;break;}
if(x&1)x>>=1;//二进制结尾是1可以通过2*y+1转化过来
else if(x&2)break;//二进制结尾是10不能再分解了
else x>>=2;//二进制结尾是00可以通过4*y转化过来
}
if(ck)vis.insert(a[i]);
}
for(auto it:vis)dp[(int)floor(log2(it))]++;
long long ans=0;
for(int i=0;i<p;i++)
{
if(i>=1)dp[i]=(dp[i]+dp[i-1])%mod;
if(i>=2)dp[i]=(dp[i]+dp[i-2])%mod;
ans=(ans+dp[i])%mod;
}
printf("%lld\n",ans);
return 0;
}

浙公网安备 33010602011771号