【nowcoder 218399】小G的LY数对

小G的LY数对

题目链接:nowcoder 218399

到牛客看:

——>点我跳转<——

题目大意

有两个数组,要你各选一个数,使它们异或的值在二进制中恰好有两位是 1。
问你有多少种选法。

思路

我们看到异或,考虑从位数处理。

看到只要两位不同,我们可以考虑用这一个方法(类似折半?)。
把两边每个数对于每一位取反得到的数互相匹配,看有多少个相同的且在两边的。
那直接匹配相同我们可以用哈希表来实现。

那接着我们就会发现一个问题,就是可以能两个数取反的位置都是相同的,然后它们这样相同了,那它不能算,而我们会算上。
那我们考虑容斥,把这种情况减去。
因为取反的是同一位,那再取反就说明等于没取反,那我们就只用找两边有多少对相同的,把答案减去这个个数 × 30 \times 30 ×30 即可。
要乘 30 30 30 是因为每一位都可以这样,就每对有三十次要减去。

然后就好了。

代码

#include<cstdio>
#include<cstring>
#define mo 1000007
#define ll long long

using namespace std;

struct Hash {
	int x, to, nxt;
}e[3000007];
int le[3000007], KK;
ll n, m, a[500001], b[500001];
ll ans;

void Hash_push(int now) {//哈希操作
	ll pl = now % mo;
	for (int i = le[pl]; i; i = e[i].nxt)
		if (e[i].to == now) {
			e[i].x++;
			return ;
		}
	e[++KK] = (Hash){1, now, le[pl]}; le[pl] = KK;
}

ll Hash_ask(ll now) {
	ll pl = now % mo;
	for (int i = le[pl]; i; i = e[i].nxt)
		if (e[i].to == now) return e[i].x;
	return 0;
}

int main() {
	scanf("%lld %lld", &n, &m);
	for (int i = 1; i <= n; i++) scanf("%lld", &a[i]);
	for (int i = 1; i <= m; i++) scanf("%lld", &b[i]);
	
	for (int i = 1; i <= n; i++)
		for (int j = 0; j < 30; j++)
			Hash_push(a[i] ^ (1 << j));//插入一边异或一个位置
	
	for (int i = 1; i <= m; i++)
		for (int j = 0; j < 30; j++)
			ans += 1ll * Hash_ask(b[i] ^ (1 << j));//寻找另一边也异或一个位置和是否有前面一边异或一个位置一样的
	
	memset(le, 0, sizeof(le));//去掉异或的位置是同一个位置的情况
	memset(e, 0, sizeof(e));
	KK = 0;
	for (int j = 1; j <= n; j++)
		Hash_push(a[j]);
	for (int j = 1; j <= m; j++)
		ans -= Hash_ask(b[j]) * 30ll;
	
	printf("%lld", ans >> 1);
	
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值