分析:
(将一个二进制数的每一位
1
1
1的下标作元素构成一个集合)
i
i
i&
n
n
n=
i
i
i,那么就是说i中的
1
1
1是
n
n
n中的子集。
i
i
i的个数即为
n
n
n的子集的个数。
i
i
i&
j
j
j=
0
0
0,那么说
i
i
i与
j
j
j没有共同位上的
1
1
1,即交集为空集。故对于一个确定的
i
>
1
i>1
i>1,j的个数有
2
m
2^{m}
2m个(
m
m
m:
i
i
i中最高位
1
1
1后的
0
0
0的个数)
所以可以想到从后往前枚举,即从小到大枚举i的最高位的位数,例如
n
n
n=
11010
11010
11010
先枚举
i
i
i=
1
1
1
0
0
0,再枚举
i
i
i=
1
1
1
0
0
0
_
\_
_
0
0
0的所有情况(
_
\_
_ 表示可以填入
0
0
0和
1
1
1的空位),然后是
i
i
i=
1
1
1
_
\_
_
0
0
0
_
\_
_
0
0
0。
分析到了这里就很容易想到动态规划的方法了。定义
d
p
[
u
]
dp[u]
dp[u]为第
u
u
u个枚举状态(即从后往前i的最高位取第u个1)时的
i
j
ij
ijpair的数目,那么可得:
d
p
[
u
]
=
∑
v
=
1
u
−
1
d
p
[
v
]
⋅
2
k
u
,
v
−
1
+
2
l
dp\left[ u\right] =\sum ^{u-1}_{v=1}dp\left[ v\right] \cdot 2^{k_{u,v}-1}+2^{l}
dp[u]=v=1∑u−1dp[v]⋅2ku,v−1+2l
k
u
,
v
k_{u,v}
ku,v:从第
v
v
v个
1
1
1到第
u
u
u个
1
1
1间的下标差
l
l
l :第
u
u
u个
1
1
1后面的二进制数的个数
公式的具体解释:
以上面的
n
=
11010
n=11010
n=11010 为例,
i
i
i=
1
1
1
_
\_
_
0
0
0
_
\_
_
0
0
0(
u
=
3
u=3
u=3)可以分解为
i
i
i=
1
1
1 [
1
1
1
0
0
0
_
\_
_
0
0
0] (
u
=
2
u=2
u=2)
和
i
i
i=
1
1
1
0
0
0
0
0
0 [
1
1
1
0
0
0] (
u
=
1
u=1
u=1)
和
i
i
i=
1
1
1
0
0
0
0
0
0
0
0
0
0
0
0
其中[]中的部分分别是
u
=
2
,
1
u=2,1
u=2,1时的情况,这样我们就把
u
=
3
u=3
u=3的情况分成了
u
=
2
,
1
u=2,1
u=2,1的情况和最高位全是0的情况。而
u
=
3
u=3
u=3和
u
=
1
u=1
u=1中间相差了两个0,这两个0在
u
=
1
u=1
u=1的基础上又给j带来了两个不确定位,所以加上的是
d
p
[
1
]
⋅
2
2
dp[1]\cdot 2^{2}
dp[1]⋅22,这也就得到了公式的第一部分
∑
v
=
1
u
−
1
d
p
[
v
]
⋅
2
k
−
1
\sum ^{u-1}_{v=1}dp\left[ v\right] \cdot 2^{k-1}
∑v=1u−1dp[v]⋅2k−1。而第二部分很简单,就是最高位全是0的情况。
虽然得到了公式,但是这个公式的带来的时间复杂度(最坏为
O
(
∣
S
∣
2
)
O(|S|^{2})
O(∣S∣2))仍然不理想。不过容易发现是可以转化成为递推式的。
d
p
[
u
+
1
]
=
∑
v
=
1
u
d
p
[
v
]
⋅
2
k
u
+
1
,
v
−
1
+
2
l
+
k
u
+
1
,
u
dp\left[ u+1\right] =\sum ^{u}_{v=1}dp\left[ v\right] \cdot 2^{k_{u+1,v}-1}+2^{l+k_{u+1,u}}\quad
dp[u+1]=v=1∑udp[v]⋅2ku+1,v−1+2l+ku+1,u
= ( ∑ v = 1 u d p [ v ] ⋅ 2 k u , v − 1 + 2 l ) ⋅ 2 k u + 1 , u \qquad\qquad=(\sum ^{u}_{v=1}dp\left[ v\right] \cdot 2^{k_{u,v}-1}+2^{l})\cdot2^{k_{u+1,u}} =(v=1∑udp[v]⋅2ku,v−1+2l)⋅2ku+1,u
= ( ∑ v = 1 u − 1 d p [ v ] ⋅ 2 k u , v − 1 + 2 l + d p [ u ] ⋅ 2 − 1 ) ⋅ 2 k u + 1 , u \qquad\qquad\qquad\qquad\qquad=(\sum ^{u-1}_{v=1}dp\left[ v\right] \cdot 2^{k_{u,v}-1}+2^{l}+dp\left[ u\right]\cdot2^{-1})\cdot2^{k_{u+1,u}} =(v=1∑u−1dp[v]⋅2ku,v−1+2l+dp[u]⋅2−1)⋅2ku+1,u
= ( d p [ u ] + d p [ u ] ⋅ 2 − 1 ) ⋅ 2 k u + 1 , u \quad\qquad=(dp\left[ u\right]+dp\left[ u\right]\cdot2^{-1})\cdot2^{k_{u+1,u}} =(dp[u]+dp[u]⋅2−1)⋅2ku+1,u
=
3
⋅
2
k
u
+
1
,
u
−
1
⋅
d
p
[
u
]
=3\cdot 2^{k_{u+1,u}-1} \cdot dp[u]\quad
=3⋅2ku+1,u−1⋅dp[u]
第一次用LaTex,等号不会对齐[捂脸]
这下我们就得到了简洁的递推式 :
d p [ u + 1 ] = 3 ⋅ 2 k u + 1 , u − 1 ⋅ d p [ u ] dp\left[ u+1\right]=3\cdot 2^{k_{u+1,u}-1} \cdot dp[u] dp[u+1]=3⋅2ku+1,u−1⋅dp[u]
时间复杂度降低到了
O
(
∣
S
∣
)
O(|S|)
O(∣S∣)。不计字符串的存储空间,空间复杂度为
O
(
1
)
O(1)
O(1)。
注意:上面我们求的情况中没有包含
i
=
0
i=0
i=0的情况,所以记得最后将计算结果加
1
1
1
AC代码:
#include<iostream>
#include<string>
using namespace std;
const int MAX_N=1e5+10;
const int MOD=1e9+7;
typedef long long ll;
int main()
{
ll sum=0,now=0,last=0;
string s;
int t;
cin>>t;
while(t--)
{
cin>>s;
int i;
for(i=s.size()-1,now=1;i>=0&&s[i]!='1';i--,now=(now*2)%MOD);//从后往前找到第一个1
if(i<0){cout<<1<<'\n';continue;}//n=0
sum=(now+1)%MOD;
last=now;
now=1;
for(i--;i>=0;i--)
if(s[i]=='1')
{
last=(now*3*last)%MOD;
sum=(last+sum)%MOD;
now=1;
}
else now=(now*2)%MOD;
cout<<sum%MOD<<'\n';
}
return 0;
}