牛客小\白月赛40——C 数字匹配(map,二进制)

题目链接


题意:

给定一个数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)21=(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 位所表示的数。

猜你喜欢

转载自blog.csdn.net/Mr_dimple/article/details/121301125