题目:
样例&说明:
大致翻译:
给出 n n n个不同数和 p p p(集合 S S S中所有元素均小于 2 p 2^p 2p),维护集合 S S S,该集合里的元素 x x x满足:
1.
x
=
a
i
1. x=a_i
1.x=ai
2.
x
=
2
y
+
1
,
y
∈
S
2.x=2y+1,y \in S
2.x=2y+1,y∈S
3.
x
=
4
y
,
y
∈
S
3.x=4y,y \in S
3.x=4y,y∈S
问集合S中满足条件的数有多少。
思路:
由于
1
<
=
p
<
=
2
∗
1
0
5
1<=p<=2*10^5
1<=p<=2∗105的值所以
2
p
2^p
2p超long long可以考虑转换二进制的角度来思考题目中条件的运用。
x
=
2
y
+
1
x=2y+1
x=2y+1即:x左移一位,补一个1(即加一);
x
=
4
y
x=4y
x=4y即:x左移两位,补两个0;
这里我们可以用dp[i]表示:进i位的情况数目;
dp[i]=dp[i-1]+dp[i-2](由条件2,3得到)
再用f[i]表示:1–i位得到的情况总数;
f[i]=f[i-1]+dp[i];
注意:因为这里的情况总数包括其本身,所以f[0]=1(即不进位时有1种情况,原数自己)
为了防止a[x]在条件变化过程中可能变为a[y]所以用map标记一下记录过的数(有点类似于找树的根节点,每个能记录为答案的都为不同的根结点)
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N=2e5+10;
const ll mod=1e9+7;
const double eps=1e-6;
ll dp[N],f[N],a[N],b[N];
map<int,int>mp;
signed main(){
int n,p;cin>>n>>p;
dp[0]=1,dp[1]=1;
f[0]=1,f[1]=2;
for(int i=2;i<=p;i++){//预处理dp、f得到对应的值
dp[i]=(dp[i-1]+dp[i-2])%mod;
f[i]=(f[i-1]+dp[i])%mod;
}
for(int i=0;i<n;i++){
cin>>a[i];mp[a[i]]++;
}
sort(a,a+n);int num=0;
for(int i=0;i<n;i++){
ll x=a[i];int flag=0;
while(x){
if(mp[x]&&a[i]!=x){//如果此a[i]与前面出现过的数具有相同数根则不记录在答案中
flag=1;break;
}
if(x&1){//条件2
x>>=1;
}else if((x>>2)*4==x){//条件3
x>>=2;
}else{//如果条件2,3都不满足说明此为最小(即树根)
break;
}
}
if(!flag){//若满足要求则记录此a[i]
b[num++]=a[i];
}
}
ll ans=0;
for(int i=0;i<num;i++){
int wei=0;
while((1<<wei)<=b[i])wei++;//记录这个数用2进制表示时的位数
if(wei>p)continue;//此时这个数超出了2的p次方的范围
ans=(ans+f[p-wei])%mod;//记录此数以及它的衍生符合题意的数的个数
}
cout<<ans<<endl;
return 0;
}