题意:
给定一个数n,求所有数对 x,y (1 ≤ x<y ≤ n) 的二进制中,满足非前导零部分最大连续重合位数 ≥ k 的数对个数。(n≤2000,k≤10)
例如:175的二进制形式为(10101111
),472472的二进制形式为(111011000
),因此175175和472472最大连续重合部分为(1011
),长度为4。
思路:
其实就是判断 两个字符串的最大连续重合长度 是否大于等于k。
很容易发现:如果发现两个字符串的连续k个位置重合了,那么就是满足的。
所以就是要判断这两个字符串是否有连续k个位置是重合的。
其实这样的题目模型之前也遇到过: 判断两个字符串是否有长度为m的相同子串?
朴素做法,二重循环暴力,超时。需要在O(n)的复杂度内实现。
所以就用到一个小技巧,map标记。
遍历一遍标记出A串中所有连续k个位置,再遍历一遍B串判断当前k个位置是否已经标记过。如果是,说明A串中也有这k个位置,那么就是匹配的。
那么在这道题中,标记连续k个位置就有两种思路:
思路1:字符串哈希
因为这是一个不大于2000的数的二进制字符串,所以最长不超过11,所以可以干脆将该串映射为一个long long范围内的数。
//keeping hurry and coding calm.
typedef pair <int, int> PII;
//unordered_map <int, int> mp;
const int N = 200010, mod = 1e9 + 7;
stack<int> stk;
vector<int> v;
int T, n, m;
int a[N];
int f1[N],f2[N],p[N];
int check1(int l,int r){
return f1[r]-f1[l-1]*p[r-l+1];
}
int check2(int l,int r){
return f2[r]-f2[l-1]*p[r-l+1];
}
bool pd(int x,int y)
{
string s1,s2;
while(x){
if(x&1) s1+='1';
else s1+='0';
x >>= 1;
}
while(y){
if(y&1) s2+='1';
else s2+='0';
y >>= 1;
}
for(int i=0;i<s1.size();i++){
if(i) f1[i]=f1[i-1]*10+s1[i]-'0';
else f1[i]=s1[i]-'0';
}
for(int i=0;i<s2.size();i++){
if(i) f2[i]=f2[i-1]*10+s2[i]-'0';
else f2[i]=s2[i]-'0';
}
set<int> st; //map和set都可,都能判断容器中是否存在一个数。
int len1=s1.size(),len2=s2.size();
for(int i=0;i<=len1-m;i++){
st.insert(check1(i,i+m-1));
}
for(int i=0;i<=len2-m;i++){
if(st.count(check2(i,i+m-1))) return 1;
}
return 0;
}
signed main(){
Ios;
int x=1;
for(int i=0;i<=50;i++){
p[i]=x;
x*=10;
}
cin>>n>>m;
int cnt=0;
for(int i=1;i<=n;i++)
for(int j=i+1;j<=n;j++)
if(pd(i,j)) cnt++;
cout<<cnt;
return 0;
}
思路2:位运算
这便是这道题的特殊之处:字符串为二进制数。
所以标记的时候可以 标记这 k 位二进制所表示出的数。
所以就要通过位运算将这 k 位二进制数变为一个int数:
while(x >= 1<<(k-1)) //保证当前k位数不是在二进制前导0位置的
{
mp[x & ((1<<k)-1)] = 1; //x 与上 最后k位为1,其余位为0的数,得到最后k位。
x >>= 1; //右移1,将最后一位抛弃
}
得到 x 的最后 k 位数表示的 int 数 y:
y = x
& 最后k位为1,其余位为0的数
。
得到最后 k 位为1,其余位为 0 的数:
构造第 k+1 位为1的数,再减去 1,便得到最后 k 位都为 1 的数。
例:
(
100000
)
2
−
1
=
(
11111
)
2
(100000)_2 - 1 = (11111)_2
(100000)2−1=(11111)2
//keeping hurry and coding calm.
typedef pair <int, int> PII;
map <int, int> mp;
const int N = 200010, mod = 1e9 + 7;
stack<int> stk;
vector<int> v;
int T, n, m;
int a[N];
bool pd(int x,int y)
{
mp.clear();
while(x >= 1<<(m-1))
{
mp[x & ((1<<m)-1)] = 1;
x >>= 1;
}
while(y >= 1<<(m-1))
{
if(mp[y & ((1<<m)-1)]) return 1;
y >>= 1;
}
return 0;
}
int main(){
cin>>n>>m;
int cnt=0;
for(int i=1;i<=n;i++)
for(int j=i+1;j<=n;j++)
if(pd(i,j)) cnt++;
cout<<cnt;
return 0;
}
这种做法代码更加简洁。
经验:
两个知识点:
1.判断两个字符串是否存在长度为 m 的相同子串?——O(n)复杂度。
2.位运算得到 二进制数的任意 k 位所表示的数。