[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;
}

  

时间: 2024-08-02 05:26:33

[BZOJ 3198] [Sdoi2013] spring 【容斥 + Hash】的相关文章

bzoj 3198: [Sdoi2013]spring 题解

[原题] 3198: [Sdoi2013]spring Time Limit: 40 Sec  Memory Limit: 256 MB Submit: 253  Solved: 95 Description Input Output Sample Input 3 3 1 2 3 4 5 6 1 2 3 0 0 0 0 0 0 4 5 6 Sample Output 2 HINT [题解]这道题明明是水题,坑了我两天!!!真是伤心.发现哈希都不熟练了. 首先很容易想到是2^6枚举01状态,使得1

BZOJ 3198 SDOI2013 spring

为什么SDOI省选一年考两次容斥原理? 我们很容易发现>=k个相等时很好计算的 但是我们要求恰好k个,那么我们容斥即可 至于计算>=k个相等,首先我们枚举相等位置,对每个串对应位置做一遍hash就可以了 #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #include<cstdlib> using namespace std; type

3198: [Sdoi2013]spring【容斥原理+hash】

容斥是ans= 至少k位置相等对数C(k,k)-至少k+1位置相等对数C(k+1,k)+至少k+2位置相等对数*C(k+2,k) -- 然后对数的话2^6枚举状态然后用hash表统计即可 至于为什么要乘上一个组合数,详见 https://www.cnblogs.com/candy99/p/6616809.html 我理解的是,因为是枚举状态统计,所以会重复计算C(k+i,k)次 #include<iostream> #include<cstdio> #include<algo

BZOJ 3198 Sdoi2013 spring Hash+容斥原理

题目大意:给定n个元素,每个元素是一个六元组,求有多少对元素满足相同的位置恰好有k个 首先对于恰好有K个这种东西果断考虑容斥原理 我们2^6枚举相同的位置 恰好有k个元素相同的对数=至少有k个位置相同的对数-至少有k+1个位置相同的对数+至少有k+2个位置相同的对数-- 但是我们计数时会发现一些问题 比如下面这组样例显然是0: 2 3 1 2 3 4 5 5 1 2 3 4 6 6 但是这一对元素被加了C(4,3)次,只被减掉了C(4,4)次 因此我们将公式改成这样: 恰好有k个元素相同的对数=

BZOJ 1026--windy数(DP&amp;容斥)

1026: [SCOI2009]windy数 Time Limit: 1 Sec  Memory Limit: 162 MBSubmit: 8856  Solved: 4007[Submit][Status][Discuss] Description windy定义了一种windy数.不含前导零且相邻两个数字之差至少为2的正整数被称为windy数. windy想知道,在A和B之间,包括A和B,总共有多少个windy数? Input 包含两个整数,A B. Output 一个整数 Sample I

[BZOJ 3129] [Sdoi2013] 方程 【容斥+组合数取模+中国剩余定理】

题目链接:BZOJ - 3129 题目分析 使用隔板法的思想,如果没有任何限制条件,那么方案数就是 C(m - 1, n - 1). 如果有一个限制条件是 xi >= Ai ,那么我们就可以将 m 减去 Ai - 1 ,相当于将这一部分固定分给 xi,就转化为无限制的情况了. 如果有一些限制条件是 xi <= Ai 呢?直接来求就不行了,但是注意到这样的限制不超过 8 个,我们可以使用容斥原理来求. 考虑容斥:考虑哪些限制条件被违反了,也就是说,有哪些限制为 xi <= Ai 却是 xi

[BZOJ 1042] [HAOI2008] 硬币购物 【DP + 容斥】

题目链接:BZOJ - 1042 题目分析 首先 Orz Hzwer ,代码题解都是看的他的 blog. 这道题首先使用DP预处理,先求出,在不考虑每种硬币个数的限制的情况下,每个钱数有多少种拼凑方案. 为了避免重复的方案被转移,所以我们以硬币种类为第一层循环,这样阶段性的增加硬币. 一定要注意这个第一层循环要是硬币种类,并且初始 f[0] = 1. f[0] = 1; for (int i = 1; i <= 4; ++i) { for (int j = B[i]; j <= MaxS; +

bzoj 3622 DP + 容斥

LINK 题意:给出n,k,有a,b两种值,a和b间互相配对,求$a>b$的配对组数-b>a的配对组数恰好等于k的情况有多少种. 思路:粗看会想这是道容斥组合题,但关键在于如何得到每个a[i]大于b的组数. 不妨从整体去考虑,使用$f[n][j]$代表前n个中有j组$a[i]>b[i]$,很容易得到转移式$f[n][j]=f[n-1][j]+f[n-1][j-1]*(cnt[n]-(j-1))$,其中$cnt[i]$为比a[i]小的b[]个数 但是仔细思考该式子含义会发现,$f[n][j

bzoj 4671 异或图 —— 容斥+斯特林反演+线性基

题目:https://www.lydsy.com/JudgeOnline/problem.php?id=4671 首先,考虑容斥,就是设 \( t[i] \) 表示至少有 \( i \) 个连通块的方案数: 我们希望得到恰好有一个连通块的方案数,但这里不能直接 \( + t[1] - t[2] + t[3] - t[4] ... \),因为每个"恰好 \( i \) 个连通块"的情况并不是在各种 \( t[j] ( j<=i ) \) 中只被算了一次,而是因为标号,被算了 \(