集合|并查集|哈希

子曰:"不愤不启,不悱不发,举一隅不以三隅反,则不复也。”

孔子说:“不发奋图强时,就不启发他,不到他想说却说不出来时候,不去启发他。列举一个方面,却不能得到三个方面,就不要告诉他。”

有的时候,我们并不关心一些数据他们的关系具体怎样,我们只是关心他们是不是一伙的。

我们可以使用并查集表示一堆集合之间的关系,我们使用hash表示一个集合。

并查集是站在元素关系的角度,那些数在同一个集合。

而hash是站在集合的角度,找出这个集合有哪些元素。

https://www.luogu.com.cn/problem/P3370

我们使用字符串hash,是通过表示数的角度,也就是进制的角度。

定理 : 设 b 是正整数 , b > 1 , 那么每一个正整数 n 都可以被唯一地写成如下形式 : n = a k b k + a k − 1 b k − 1 + . . . + a 1 b + a 0 其中 k 为正整数 , a j 为整数 , 0 ≤ a j ≤ b − 1 ( j = 0 , 1 , . . . , k ) 且首项系数 a k ≠ 0 证明 : 通过使用连续的带余数除法,得到这样的表示。 首先使用 b 除 n 得到 n = b q 0 + a 0 , 0 ≤ a 0 ≤ b − 1 如果 q 0 ≠ 0 , 那么用 b 除 q 0 得到 q 0 = b q 1 + a 1 , 0 ≤ a 1 ≤ b − 1 q 1 = b q 2 + a 2 , 0 ≤ a 2 ≤ b − 1 q 2 = b q 3 + a 3 , 0 ≤ a 3 ≤ b − 1 . . . q k − 2 = b q k − 1 + a k − 1 , 0 ≤ a k − 1 ≤ b − 1 q k − 1 = b × 0 + a k , 0 ≤ a k ≤ b − 1 其中 n = b q 0 + a 0 我们使用第二个方程带入 , n = b ( b q 1 + a 1 ) + a 0 = b 2 q 1 + a 1 b + a 0 再将第三个方程带入。 n = b 2 ( b q 2 + a 2 ) + a 1 b + a 0 = b 3 q 2 + a 2 b 2 + a 1 b + a 0 . . . = b k − 1 q k − 2 . . . + a 2 b 2 + a 1 b + a 0 = b k q k − 1 + b k − 1 a k − 1 . . . . + a 2 b 2 + a 1 b + a 0 = a k b k + a k − 1 b k − 1 . . . + a 2 b 2 + a 1 b + a 0 \begin{align*} &定理:设b是正整数,b>1,那么每一个正整数n都可以被唯一地写成如下形式: \\&n=a_kb^k+a_{k-1}b^{k-1}+...+a_1b+a_0 \\&其中k为正整数,a_j 为整数,0\le a_j\le b-1(j=0,1,...,k)且首项系数a_k\ne 0 \\&证明:通过使用连续的带余数除法,得到这样的表示。 \\&首先使用b除n得到 \\&n=bq_0+a_0,0\le a_0\le b-1 \\&如果q_0\ne 0,那么用b除q_0得到 \\&q_0=bq_1+a_1,0\le a_1\le b-1 \\&q_1=bq_2+a_2,0\le a_2\le b-1 \\&q_2=bq_3+a_3,0\le a_3\le b-1 \\&... \\&q_{k-2}=bq_{k-1}+a_{k-1},0\le a_{k-1}\le b-1 \\&q_{k-1}=b\times 0+a_k,0\le a_k\le b-1 \\& 其中n=bq_0+a_0 \\&我们使用第二个方程带入,n=b(bq_1+a_1)+a_0 \\&=b^2q_1+a_1b+a_0 \\&再将第三个方程带入。 \\&n=b^2(bq_2+a_2)+a_1b+a_0 \\&=b^3q_2+a_2b^2+a_1b+a_0 \\&... \\&=b^{k-1}q_{k-2}...+a_2b^2+a_1b+a_0 \\&=b^{k}q_{k-1}+b^{k-1}a_{k-1}....+a_2b^2+a_1b+a_0 \\&=a_kb^{k}+a_{k-1}b^{k-1}...+a_2b^2+a_1b+a_0 \end{align*} 定理:b是正整数,b>1,那么每一个正整数n都可以被唯一地写成如下形式:n=akbk+ak1bk1+...+a1b+a0其中k为正整数,aj为整数,0ajb1(j=0,1,...,k)且首项系数ak=0证明:通过使用连续的带余数除法,得到这样的表示。首先使用bn得到n=bq0+a0,0a0b1如果q0=0,那么用bq0得到q0=bq1+a1,0a1b1q1=bq2+a2,0a2b1q2=bq3+a3,0a3b1...qk2=bqk1+ak1,0ak1b1qk1=b×0+ak,0akb1其中n=bq0+a0我们使用第二个方程带入,n=b(bq1+a1)+a0=b2q1+a1b+a0再将第三个方程带入。n=b2(bq2+a2)+a1b+a0=b3q2+a2b2+a1b+a0...=bk1qk2...+a2b2+a1b+a0=bkqk1+bk1ak1....+a2b2+a1b+a0=akbk+ak1bk1...+a2b2+a1b+a0
实际上我们的算法就是通过进制的思想,利用运算的技巧完善的。
设 H i = ∑ j = 1 i s j × b i − j 那么 H i − 1 = ∑ j = 1 i − 1 s j × b i − 1 − j H i = ∑ j = 1 i − 1 s j × b i − 1 + b i = b ∑ j = 1 i − 1 s j × b i − 1 = H i − 1 × b + s i 那没 [ l , r ] 段的进制如何表示用 H i 表示 ? H l , r = ∑ i = l r s i × b r − i = ∑ i = 1 r s i × b r − i − ∑ i = 1 l − 1 s i × b r − i = H r − ∑ i = 1 l − 1 s i × b r − i b l − 1 − r b r + 1 − l = H r − b r + 1 − l ∑ i = 1 l − 1 s i × b l − 1 − i = H r − b r + 1 − l H l − 1 \begin{align*} \\&设H_i=\sum_{j=1}^is_j\times b^{i-j} \\&那么H_{i-1}=\sum_{j=1}^{i-1}s_j\times b^{i-1-j} \\&H_i=\sum_{j=1}^{i-1}s_j\times b^{i-1}+b^{i} \\&=b\sum_{j=1}^{i-1}s_j\times b^{i-1} =H_{i-1}\times b+s_i \\&那没[l,r]段的进制如何表示用H_i表示? \\&H_{l,r}=\sum_{i=l}^rs_i\times b^{r-i} \\&=\sum_{i=1}^rs_i\times b^{r-i}-\sum_{i=1}^{l-1}s_i\times b^{r-i} \\&=H_r-\sum_{i=1}^{l-1}s_i\times b^{r-i}b^{l-1-r}b^{r+1-l} \\&=H_r-b^{r+1-l}\sum_{i=1}^{l-1}s_i\times b^{l-1-i} \\&=H_r-b^{r+1-l}H_{l-1} \end{align*} Hi=j=1isj×bij那么Hi1=j=1i1sj×bi1jHi=j=1i1sj×bi1+bi=bj=1i1sj×bi1=Hi1×b+si那没[l,r]段的进制如何表示用Hi表示?Hl,r=i=lrsi×bri=i=1rsi×brii=1l1si×bri=Hri=1l1si×bribl1rbr+1l=Hrbr+1li=1l1si×bl1i=Hrbr+1lHl1

但是,我们的数字可能特别大,所以我们考虑取模的问题。

但是取了模之后,就会产生新的问题,就是虽然字符串不同,但是最后的数值确实相同的。

#include<bits/stdc++.h>
using namespace std;
#define int long long  
const int b=131;
const int mod=998244353;
const int N=1e6+10;
int t,n,m;
int h[N],H[N],pw[N];
char s[N],ans[N];
int query(int l,int r)
{
	if(l>r)return 0;
	return (H[r]-H[l-1]*pw[r-l+1]%mod+mod)%mod;
}
signed main()
{
	cin>>t;
	pw[0]=1;
	for(int i=1;i<=N;i++)
	{
		pw[i]=pw[i-1]*b%mod;
	}
	for(int i=1;i<=t;i++)
	{
		cin>>(s+1);
		n=strlen(s+1);
		int l=0;
		for(int j=1;j<=n;j++)
		{
			h[j]=(h[j-1]*b%mod+s[j]);
		}
		for(int j=min(n,m);j>=0;j--)
		{
			if(h[j]==query(m-j+1,m))
			{
				l=j;
				break;
			}
		}
		for(int j=l+1;j<=n;j++)
		{
			ans[++m]=s[j];
			H[m]=(H[m-1]*b%mod+s[j])%mod;
		}
	}
	for(int i=1;i<=m;i++)cout<<ans[i];
}

https://www.luogu.com.cn/problem/P1621

当然,有的时候集合是并查集,表示两个数之间的关系。

并查集是一种很小巧的算法。

如果经过压缩,核心部分可能只有一行。

我们使用路径压缩的并查集。

当合并的时候,我们使得其中一个指向宁外一个的根节点。

#include<bits/stdc++.h>
using namespace std;
#define int long long  
const int N=100010;
int fa[N];
int is[N];
int ans,a,b,p;
int find(int x)
{
	if(x==fa[x])return x;
	return fa[x]=find(fa[x]);
}
void unity(int a,int b)
{
	a=find(a);
	b=find(b);
	if(a!=b)
	fa[a]=b;
}
void init(int n)
{
	for(int i=1;i<=n;i++)fa[i]=i;
}
signed main()
{
	cin>>a>>b>>p;
	init(b);
	for(int i=2;i<=b;i++)
	{
		if(!is[i])
		{
			int pre=0;
			for(int j=i;j<=b;j+=i)
			{
				is[j]=true;
				if(i>=p&&j>=a)
				{
					if(pre==0)
					pre=j;
					else 
					unity(pre,j);
				}
			}
		}
	}
	for(int i=a;i<=b;i++)
	{
		if(fa[i]==i)
		ans++;
	}
	cout<<ans;
}

https://www.luogu.com.cn/problem/P1525

此题蕴含了否定只否定的思想。

我们将这个问题用数学语言表达,在一张图中,
我们希望二染色,使得两个同色节点之间的边权值最小。

我们可以使用一种贪心的思维,首先使得权值最大的边不成立,然后使得权值第二大的边不成立。

这条边能不能成立取决于能不能染不同的颜色。

这就可以使用并查集了。

如果在同一个块中,他们就具有关系了,需要判断他们的关系

如果在不同块中,那么可以合并。
但是合并的时候需要判断添加的边是无权边还是加权边。

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=100010,M=3*N;
int p[N],d[N];
int n,m;
struct  Edge
{
   int a,b,w;
   bool operator<(const Edge&b)const 
   {
   	return w>b.w;
   }
}edge[M];
int find(int x)
{
   if(p[x]!=x)
   {
   	int u=find(p[x]);
   	d[x]+=d[p[x]];
   	p[x]=u;
   }
   return p[x];
}
int res=0;
signed main()
{
   cin>>n>>m;
   for(int i=1;i<=n;i++)
   {
   	p[i]=i;
   	d[i]=0;
   }
   for(int i=0;i<m;i++)
   {
   	int a,b,c;
   	cin>>a>>b>>c;
   	edge[i]={a,b,c};
   }
   sort(edge,edge+m);
   for(int i=0;i<m;i++)
   {
   	int a=edge[i].a;
   	int b=edge[i].b;
   	int w=edge[i].w;
   	int pa=find(edge[i].a);
   	int pb=find(edge[i].b);
   	if(pa==pb)
   	{
   		if((d[a]+d[b])%2==0)
   		{
   			cout<<w;
   			return 0;
   		}
   	}
   	else 
   	{
   		if((d[a]+d[b])&1)
   		{
   			p[pa]=pb;
   			d[pa]=0;
   		}
   		else {
   			p[pa]=pb;
   			d[pa]=1;
   		}
   	}
   }
   cout<<res;
}
  • 21
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值