[Sdoi2013] [bzoj 3198] spring (hash+容斥原理)

探讨了在给定条件下,如何使用hash容斥原理解决6维坐标中点对匹配问题,通过双hash技巧和广义容斥原理,实现高效算法。

题目描述

给出nnn666维坐标,求有多少对点对满足恰好mmm个位置相等
1&lt;=n&lt;=1051&lt;=n&lt;=10^51<=n<=105
0&lt;=k&lt;=60&lt;=k&lt;=60<=k<=6
坐标数值在2302^{30}230以内

题目分析

这道题一看就是hash容斥原理,用mmm个位置对应相等−(m+1)-(m+1)(m+1)个位置对应相等+(m+2)+(m+2)+(m+2)个位置对应相等的…
但是不能简简单单直接+/−+/-+/,根据广义容斥,还要乘上容斥系数CkmC_{k}^{m}Ckm
HashHashHash,过程中遇到Hash1Hash1Hash1相同但Hash2Hash2Hash2不同的就往后平移,用数组存一下Hash1Hash1Hash1kkk时的Hash2Hash2Hash2值与CntCntCnt
注意此处ModModMod数要大于nnn

考试时没用双Hash,想到了做法,奈何代码太丑,这题爆0了…

AC code
#include <bits/stdc++.h>
using namespace std;
#define LL long long
const int MAXN = 100005;
const int P1 = 137, Mod1 = 9999997;
const int P2 = 167, Mod2 = 7394895;
int num[MAXN][6], c[7][7], kase, n, m;
struct MyHash
{
	LL y; int flag, cnt; //y存的是Hash1为当前下标i时的Hash2值
	//flag是用int打标记,就不用每次清零了
	bool Exist() { return flag == kase; }
}h[Mod1];
LL Ans;
void init()
{
	for(int i = 0; i < 7; ++i)
	{
		c[i][0] = c[i][i] = 1;
		for(int j = 1; j < i; ++j)
			c[i][j] = c[i-1][j-1] + c[i-1][j];
	}
}

inline void MyUnique(LL &x, LL o)
{
	while(h[x].Exist() && h[x].y != o) (++x) %= Mod1;
}

bool used[7];
void dfs(int pos, int tot)//枚举当前是求哪几个位置
{
	if(pos == 6)
	{
		if(tot < m) return; //小于m的不用处理
		LL sum = 0; ++kase;
		for(int i = 1; i <= n; ++i)
		{
			LL hsh1 = 0, hsh2 = 0;
			for(int j = 0; j < 6; ++j) if(used[j])
				hsh1 = (hsh1 * P1 % Mod1 + num[i][j]) % Mod1,
				hsh2 = (hsh2 * P2 % Mod2 + num[i][j]) % Mod2;
			MyUnique(hsh1, hsh2);
			if(h[hsh1].flag < kase)
				h[hsh1].cnt = 0, h[hsh1].flag = kase;
			h[hsh1].y = hsh2, sum += (h[hsh1].cnt++);
		}
		Ans += sum * (((tot-m)&1) ? -1 : 1) * c[tot][m]; //容斥
		return;
	}
	used[pos] = 1;
	dfs(pos+1, tot+1);
	used[pos] = 0;
	dfs(pos+1, tot);
}

int main ()
{
	scanf("%d%d", &n, &m); init();
	for(int i = 1; i <= n; ++i)
		for(int j = 0; j < 6; ++j)
			scanf("%d", &num[i][j]);
	dfs(0, 0);
	printf("%lld\n", Ans);
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值