子曰:"不愤不启,不悱不发,举一隅不以三隅反,则不复也。”
孔子说:“不发奋图强时,就不启发他,不到他想说却说不出来时候,不去启发他。列举一个方面,却不能得到三个方面,就不要告诉他。”
有的时候,我们并不关心一些数据他们的关系具体怎样,我们只是关心他们是不是一伙的。
我们可以使用并查集表示一堆集合之间的关系,我们使用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+ak−1bk−1+...+a1b+a0其中k为正整数,aj为整数,0≤aj≤b−1(j=0,1,...,k)且首项系数ak=0证明:通过使用连续的带余数除法,得到这样的表示。首先使用b除n得到n=bq0+a0,0≤a0≤b−1如果q0=0,那么用b除q0得到q0=bq1+a1,0≤a1≤b−1q1=bq2+a2,0≤a2≤b−1q2=bq3+a3,0≤a3≤b−1...qk−2=bqk−1+ak−1,0≤ak−1≤b−1qk−1=b×0+ak,0≤ak≤b−1其中n=bq0+a0我们使用第二个方程带入,n=b(bq1+a1)+a0=b2q1+a1b+a0再将第三个方程带入。n=b2(bq2+a2)+a1b+a0=b3q2+a2b2+a1b+a0...=bk−1qk−2...+a2b2+a1b+a0=bkqk−1+bk−1ak−1....+a2b2+a1b+a0=akbk+ak−1bk−1...+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=1∑isj×bi−j那么Hi−1=j=1∑i−1sj×bi−1−jHi=j=1∑i−1sj×bi−1+bi=bj=1∑i−1sj×bi−1=Hi−1×b+si那没[l,r]段的进制如何表示用Hi表示?Hl,r=i=l∑rsi×br−i=i=1∑rsi×br−i−i=1∑l−1si×br−i=Hr−i=1∑l−1si×br−ibl−1−rbr+1−l=Hr−br+1−li=1∑l−1si×bl−1−i=Hr−br+1−lHl−1
但是,我们的数字可能特别大,所以我们考虑取模的问题。
但是取了模之后,就会产生新的问题,就是虽然字符串不同,但是最后的数值确实相同的。
#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;
}