POJ3274 牛的属性和(数的哈希)

给定多头牛的属性,每头牛的属性由一个非负数表示,该数的二进制表示不会超过K位,它的二进制表示的每一位若为1则表示该牛有对应的第i种属性,若为0则表示没有该属性。

对于给定的牛的顺序,要求输出某一段子序列的长度,这个子序列中的牛的K个属性对应相加以后全部相等。

假设n=3, k = 3

输入的3个数变成的二进制分别为(a1, a2, a3), (b1, b2, b3), (c1, c2, c3)

设sum(i)为从第1个数到第i个数的属性和的序列

若从第2个数到第3个数的序列满足条件,则说明b1+c1 = b2+c2 = b3+c3,即sum(3)-sum(2)的序列每一位都相等

推广一下,若sum(i) = (a, b, c),sum(j) = (d, e, f),且i到j这个子序列满足条件,则说明(d, e, f) - (a, b, c) = (x, x, x),即(d, e, f) = (a + x, b + x, c + x)。每个序列中的数都减去序列中的最后一个数,得到(d - f, e - f, 0) = (a - c, b - c, 0)。因此只要判断两个完全转换过后的序列是否相同,就可以知道它们之间的原序列是否满足条件了。

所以解题的第一步是把原来的数转换为二进制序列,第二步是把二进制序列转换成sum序列,即逐步叠加,第三步是把每个sum序列都减去该序列的最后一个数,最后一步是把这些序列进行哈希,计算它们的最大差距。

有一点要注意,如果从第1个数到第i个数这段序列满足条件,即sum(i) - sum(0) = (x, x, x),则说明sum(i)的各个位都是相同的,因此需要在第三步之前先做这个判断,把符合条件的序列找出来,更新一下答案。

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;

const int N = 100010;
const int K = 32;
const int H = 100007;
int n, k, ans;
int num[N][K];

struct Node
{
	int pos;
	int next;
};
Node node[N];
int cur;
int hashTable[H];

bool cmp(int x1, int x2)
{
	for (int i = 0; i < k; ++i)
	{
		if (num[x1][i] != num[x2][i]) return false;
	}
	return true;
}

bool allsame(int x)
{
	for (int i = 0; i < k - 1; ++i) 
	{
		if (num[x][i] != num[x][i + 1]) return false;
	}
	return true;
}

void init()
{
	for (int i = 0; i < H; ++i) hashTable[i] = -1;
	ans = 0;
	cur = 0;
}

unsigned int getHash(int x)
{
	unsigned int hash = 0;
	for (int i = 0; i < k; ++i)
	{
		hash += num[x][i] * (i + 1);
	}
	return (hash & 0x7fffffff) % H;
}

void searchHash(int x)
{
	unsigned int h = getHash(x);
	int next = hashTable[h];
	while (next != -1)
	{
		if (cmp(node[next].pos, x))
		{
			if (x - node[next].pos > ans) ans = x - node[next].pos;
			return;
		}
		next = node[next].next;
	}
	node[cur].pos = x;
	node[cur].next = hashTable[h];
	hashTable[h] = cur;
	++cur;
}

int main()
{
	int val;
	init();
	scanf("%d%d", &n, &k);
	for (int i = 0; i < n; ++i)
	{
		scanf("%d", &val);
		for (int j = 0; j < k; ++j)
		{
			num[i][j] = (val & 1);
			val >>= 1;
		}
	}
	for (int i = 1; i < n; ++i)
	{
		for (int j = 0; j < k; ++j)
		{
			num[i][j] += num[i - 1][j];
		}
	}
	for (int i = n - 1; i >= 0; --i)
	{
		if (allsame(i)) 
		{
			ans = i + 1;
			break;
		}
	}
	for (int i = 0; i < n; ++i)
	{
		for (int j = 0; j < k; ++j)
		{
			num[i][j] -= num[i][k - 1];
		}
	}
	for (int i = 0; i < n; ++i) searchHash(i);
	printf("%d\n", ans);
	return 0;
}


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值