[BZOJ 3198] [Sdoi2013] spring 【容斥 + Hash】

题目链接:BZOJ - 3198

 

题目分析

题目要求求出有多少对泉有恰好 k 个值相等。

我们用容斥来做。

枚举 2^6 种状态,某一位是 1 表示这一位相同,那么假设 1 的个数为 x 。

答案就是 sigma((-1)^(x - k) * AnsNow * C(x, k)) 。注意 x 要大于等于 k。

对于一种状态,比如 10110,就是要保证第 1, 3, 4 个值相同。

这些值相同的对数怎么来求呢?使用Hash。

将这些位上的值 Hash 成一个数,然后枚举  [1, i] , 每次求出 [1, i-1] 有多少和 i 相同的,再把 i 加入 Hash 表。

 

代码

#include <iostream>
#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <cmath>
#include <algorithm>

using namespace std;

typedef long long LL;

const int MaxN = 100000 + 5, Base = 11003, Mod = 10007;

int n, k;
int A[MaxN][10];

LL Ans, Temp;
LL C[10][10];

void PrepareC()
{
	C[0][0] = 1;
	for (int i = 1; i <= 6; ++i)
	{
		C[i][0] = 1;
		for (int j = 1; j <= i; ++j)
			C[i][j] = C[i - 1][j - 1] + C[i - 1][j];
	}
}

struct HashNode
{
	int B[10];
	int Sum;
	HashNode *Next;
} HA[MaxN], *P = HA, *Hash[Mod + 5];

inline bool Cmp(int *x, int *y)
{
	for (int i = 0; i < 6; ++i)
		if (x[i] != y[i]) return false;
	return true;
}

int Get(int x, int k)
{
	int Num[10];
	LL HashNum = 0;
	for (int i = 0; i < 6; ++i)
	{
		HashNum = HashNum * Base % Mod;
		if (k & (1 << i)) 
		{
			Num[i] = A[x][i];
			HashNum += Num[i] % Base;
			HashNum %= Mod;
		}
		else Num[i] = 0;
	}
	HashNode *Now;
	Now = Hash[HashNum];
	int ret = 0;
	while (Now)
	{
		if (Cmp(Now -> B, Num))
		{
			ret = Now -> Sum;
			break;
		}
		Now = Now -> Next;
	}
	return ret;
}

void Add(int x, int k)
{
	int Num[10];
	LL HashNum = 0;
	for (int i = 0; i < 6; ++i)
	{
		HashNum = HashNum * Base % Mod;
		if (k & (1 << i)) 
		{
			Num[i] = A[x][i];
			HashNum += Num[i] % Base;
			HashNum %= Mod;
		}
		else Num[i] = 0;
	}
	HashNode *Now;
	Now = Hash[HashNum];
	bool Flag = false;
	while (Now)
	{
		if (Cmp(Now -> B, Num))
		{
			Flag = true;
			++(Now -> Sum);
			break;
		}
		Now = Now -> Next;
	}
	if (Flag) return;
	++P; P -> Sum = 1;
	for (int i = 0; i < 6; ++i) P -> B[i] = Num[i];
	P -> Next = Hash[HashNum]; Hash[HashNum] = P;
}

int main()
{
	scanf("%d%d", &n, &k);
	PrepareC();
	for (int i = 1; i <= n; ++i)
		for (int j = 0; j < 6; ++j)
			scanf("%d", &A[i][j]);
	int Cnt;
	Ans = 0;
	for (int i = 0; i < 64; ++i)
	{
		Cnt = 0;
		for (int j = 0; j < 6; ++j) 
			if (i & (1 << j)) ++Cnt;
		if (Cnt < k) continue;
		Temp = 0;
		memset(Hash, 0, sizeof(Hash));
		P = HA;
		for (int j = 1; j <= n; ++j)
		{
			Temp += Get(j, i);
			Add(j, i);
		}
		if ((Cnt - k) & 1) Temp *= -1;
		Ans += Temp * C[Cnt][k];
	}
	printf("%lld\n", Ans);
	return 0;
}

  

posted @ 2015-04-07 10:59  JoeFan  阅读(318)  评论(0编辑  收藏  举报